C++ 从磁盘读取文件并将其写入共享内存

C++ Read file from disk and write it into shared memory

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

我想从磁盘读取一个文件(假设它是一个图像文件)并将其写入共享内存,这样我就可以从另一个进程的共享内存中读取它。 首先,我按照 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 问题上找到了这句话:

Notice: A std::string is suitable for holding a 'binary' buffer, where a std::wstring is not!

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

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

我不会提供完整的答案,因为我没有 MCVE at hand. However, the OP asked for more clarification and, concerning CopyMemory() 我发现了一些值得注意的事情(而且只写评论有点太长了) ).

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

在谷歌搜索 CopyMemory() 时,我偶然发现了“CopyMemory() vs. memcpy()”,并在 GameDev:

上找到了一个简短的答案

Straight out of WINBASE.H

#define CopyMemory RtlCopyMemory

Then, straight out of WINNT.H

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

所以,我们在这里:

std::memcpy()

Defined in header <cstring>

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

Copies count bytes from the object pointed to by src to the object pointed to by dest. Both objects are reinterpreted as arrays of unsigned char.

If the objects overlap, the behavior is undefined.

对于(可能)重叠 source/destination 范围的特殊情况,memcpy() 有一个 "sibling" memmove()。在这种内存映射文件的情况下,我认为源和目标永远不会重叠。因此,memcpy() 可能没问题(甚至可能比 memmove() 更快)

因此,提供 "Memory Mapped File Access Magic" 的不是 CopyMemory()。这已经发生在另一个函数调用中,它肯定在 OP 的源代码中,但在问题中没有提到:

MapViewOfFile()

Maps a view of a file mapping into the address space of a calling process.

Return Value

If the function succeeds, the return value is the starting address of the mapped view.

因此,如果成功,MapViewOfFile() returns 指向文件映射到的内存的指针。 Read/write 访问可以像任何其他进程内存访问一样在之后完成——通过赋值运算符,通过 memcpy()(或 CopyMemory()),或者其他任何可以想象的方式。

最后OP的附加题答案:

how I could read the data into a string/byte-array on the "other" side where I read from the shared memory?

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

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

在OP的具体情况下,第一个选项可能更合理。因此,有效负载数据(图像)的大小也可能存储在内存映射文件中。在 reader 方面,首先评估大小(它必须具有特定的 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);

在 reader 方面它可能看起来像这样:

/* 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, '[=13=]');
// read pay-load
CopyMemory(&data[0], pBuf + sizeof size, size);

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