【问题标题】:C++ Read file from disk and write it into shared memoryC++ 从磁盘读取文件并将其写入共享内存
【发布时间】:2018-10-16 08:01:41
【问题描述】:

我的目标是实现以下目标:

我想从磁盘读取一个文件(假设它是一个图像文件)并将其写入共享内存,以便我可以从另一个进程的共享内存中读取它。 首先,我按照this msdn tutorial 创建了一个简单的共享内存实现来包含一个字符串。它工作正常。

然后我找到了一种从磁盘读取图像的方法。实现如下:

std::ifstream fin("path/to/img.png", std::ios::in | std::ios::binary);
std::ostringstream oss;
oss << fin.rdbuf();
std::string data(oss.str());

所以现在我有一个包含我的数据的std::string data.length() 表示我已读取的文件已成功存储在其中。在 msdn 示例中,MapViewOfFile 结果的类型是LPTSTR,所以我寻找一种将std::string 强制转换为LPTSTR 的方法,据我所知是const wchar_t*。我这样做如下:

std::wstring widestr = std::wstring(data.begin(), data.end());
const wchar_t* widecstr = widestr.c_str();

但如果我现在检查_tcslen(widecstr),结果是4。所以我想我试图做的事情是行不通的。我还在另一个 SO 问题上找到了这句话:

注意:std::string 适合存放“二进制”缓冲区,而 std::wstring 则不是!

(Source) 这听起来好像我无法按照我尝试的方式存储文件数据。

所以我的问题是:我只是在某个地方犯了错误还是我的方法有缺陷?也许我需要为MapViewOfFile 的结果使用另一种文件类型?也许我需要将文件初始化加载到另一种类型?

【问题讨论】:

  • 当您将图像文件加载到std::string 时,您“滥用”了char 作为字节的表示。这没关系,我也会这样做。 MSDN 的共享内存示例处理可能使用宽字符的文本字符串,具体取决于TCHAR 的定义方式。忘记这一点 - 这与您的情况无关。 CopyMemory((PVOID)pBuf, data.data(), data.size()); 应该没问题。
  • 题外话,但是为了让你的代码可移植,最好使用boost.interprocess而不是使用Win32 API
  • @Scheff 谢谢,看起来这行得通。如果您将其作为答案,我可以接受。另外,您能否告诉我如何将数据读入从共享内存读取的“另一”端的字符串/字节数组中?

标签: c++ winapi shared-memory lptstr


【解决方案1】:

我不会提供完整的答案,因为我手头没有MCVE。然而,OP 要求进一步澄清,关于CopyMemory(),我发现了一些值得注意的事情(仅对此发表评论有点太长了)。

CopyMemory() 并不是专门用于内存映射文件的。它只是一个将数据从源复制到目标的函数,大小以字节为单位。

在搜索 CopyMemory() 时,我偶然发现了“CopyMemory()memcpy()”,并在 GameDev 上找到了一个简短的答案:

直接出WINBASE.H

#define CopyMemory RtlCopyMemory

然后,直接退出WINNT.H

#define RtlCopyMemory(Destination,Source,Length) memcpy((Destination),(Source),(Length))

所以,我们来了:

std::memcpy()

在标题&lt;cstring&gt;中定义

void* memcpy( void* dest, const void* src, std::size_t count );

count 字节从src 指向的对象复制到dest 指向的对象。两个对象都被重新解释为 unsigned char 的数组。

如果对象重叠,则行为未定义。

对于(可能)重叠源/目标范围的特殊情况,memcpy() 有一个“兄弟”memmove()。在这种内存映射文件的情况下,我不相信源和目标可以重叠。所以,memcpy() 可能没问题(甚至可能比memmove() 更快。)

所以,不是CopyMemory() 提供了“内存映射文件访问魔法”。这已经发生在另一个函数调用中,该函数调用肯定在 OP 的源代码中,但问题中没有提到:

MapViewOfFile()

将文件映射视图映射到调用进程的地址空间。

返回值

如果函数成功,则返回值是映射视图的起始地址。

因此,在成功时,MapViewOfFile() 返回一个指向文件已映射到的内存的指针。之后可以像任何其他进程内存访问一样进行读/写访问——通过赋值运算符、通过memcpy()(或CopyMemory())或其他任何可以想象的方式。

最后,OP的附加问题的答案:

如何将数据读入从共享内存读取的“另一端”的字符串/字节数组?

读取可以以完全相同的方式完成,除了指向地图视图的指针成为源,本地缓冲区成为目标。但是如何确定大小?这个问题其实更笼统:变长的数据占用了多少字节? C/C++中有两个典型的答案:

  • 存储数据的大小(如std::stringstd::vector 等)
  • 或以某种方式备注数据的结尾(例如 C 字符串中的零终止符)。

在 OP 的具体情况下,第一个选项可能更合理。因此,有效负载数据(图像)的大小也可能存储在内存映射文件中。在阅读器方面,首先评估大小(它必须具有特定的int 类型,因此具有已知的字节数),然后使用大小来复制有效负载数据。

因此,在作者方面,它可能如下所示:

/* prior something like
 * unsigned char *pBuf = MapViewOfFile(...);
 * has been done.
 */
// write size:
size_t size = data.size();
CopyMemory(pBuf, (const void*)&size, sizeof size);
// write pay-load from std::string data:
CopyMemory(pBuf + sizeof size, data.data(), size);

在阅读器方面,它可能如下所示:

/* prior something like
 * const unsigned char *pBuf = MapViewOfFile(...);
 * has been done.
 */
// read size:
size_t size = 0;
CopyMemory((void*)&size, pBuf, sizeof size);
// In C, I had probably done: size_t size = *(size_t*)pBuf; instead...
// allocate local buffer for pay-load
std::string data(size, '\0');
// read pay-load
CopyMemory(&data[0], pBuf + sizeof size, size);

请注意&amp;data[0] 提供的地址与data.data() 相同。在 C++ 17 之前,没有 std::string::data() 的非常量版本,因此 std::string::operator[]() 的 hack 具有非常量版本。

【讨论】:

    猜你喜欢
    • 2016-12-09
    • 2017-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-26
    • 2015-07-19
    相关资源
    最近更新 更多