Windows 下利用共享内存实现进程间通信

本文主要记载了在 Windows 下如何通过调用 WinApi ,利用 共享内存 实现 进程间通信 (Inter Process Communication, IPC).

本文主要是通过 FileMapping 的方式实现进程间通信。目前已实现进程间共享字符串以及 opencv 的 Mat 对象 (以 uchar * 方式)。


两个 C++ 进程之间的通信

参考文章 C++共享内存实现(windows和linux)

上面的文章只实现了字符串的进程间通信,根据实际需要,需要进程间传递图像对象,此处使用 opencv 的 Mat 对象来表示。由于 Mat 对象有一个 uchar* data 属性,指向 Mat 的实际数据。因此,这里通过传输 uchar* data 到共享内存,并在 Reader 中重建 Mat 对象 (C++)。
参考文章 Windows进程间通信–共享内存映射文件(FileMapping).

注意: 这里在传递时需要将图像的 长、宽、高 等参数一并传递,这样在 Reader 中可以根据读到的尺寸进行恢复。因为在 Reader 端一开始得到的是共享内存的首地址。C++ 中用三个 int 类型来表示 width, height, channel。这三个 int 是写到表示数据的 uchar * 的最前面的。

1
2
3
4
5
6
7
// code snippet in img_reader.cpp
int width, height;
uchar* tmp = (uchar*) malloc(buffer_size);
memcpy(tmp, &width, sizeof(width)) // 将 width 首地址开始的 4 个 byte 内容拷贝到 tmp 所指向的地址。
tmp += sizeof(width); // tmp 向后移动 sizeof(width) 个 uchar 的长度。
memcpy(tmp, &height, sizeof(height))
tmp += sizeof(height)

C++ 和 python 进程之间的通信

C++ 作 Writer, python 作 Reader
参考文章:Shared Memory Example (Python, ctypes, VC++)
python 中 使用 ctypes 来调用 win32 api,方法与 C++ Reader 中的类似。区别在于获取共享内存的首地址后,python 中可以通过 slicing 的方式获取不同地址的值。最后得到 width, height, channel 以及数据,将数据转换成 ndarray 即可使用 opencv 显示。


FileMapping 原理

这里使用了 FileMapping 的方式进行 IPC。
Writer 使用的 API 有

CreateFile
CreateFileMapping
MapViewOfFile
MapViewOfFile 最后返回的是共享内存的指针。通过将数据 memcpy 到这里实现共享。

Reader 使用的 API 有

OpenFileMapping
MapViewOfFile
MapViewOfFile 最后返回的是共享内存的指针。可通过从这里读数据重建图像。

关于以上提及 API 的详细解释:

Windows核心编程-CreateFile详解


File Mapping 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// img_writer.cpp
#include <Windows.h>
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <vector>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>

using namespace std;
using namespace cv;

//struct MyData
//{
// int width;
// int height;
// int channel;
// MyData(int _width, int _height, int _channel) : width(_width), height(_height), channel(_channel)
// {}
//};

void writeMemory(char* imgDir) {
// define shared data
char *shared_file_name = "file_name_sean";
char * shared_object_name = "Local\\object_name_sean";

Mat img = imread(imgDir, IMREAD_COLOR);

int width = img.cols;
int height = img.rows;
int channel = img.channels();
uchar* img_data = img.data;

unsigned long data_size = sizeof(uchar) * width * height * channel;
unsigned long buffer_size = data_size + 3 * sizeof(int);

cout << "share buffer " << endl;

// create shared memory file
HANDLE hFile = CreateFile(shared_file_name,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_ALWAYS, // open exist or create new, overwrite file
FILE_ATTRIBUTE_NORMAL,
NULL);

if (hFile == INVALID_HANDLE_VALUE)
cout << "create file error" << endl;

HANDLE shared_file_handler = CreateFileMapping(
hFile, // Use paging file - shared memory
NULL, // Default security attributes
PAGE_READWRITE, // Allow read and write access
0, // High-order DWORD of file mapping max size
buffer_size, // Low-order DWORD of file mapping max size
shared_object_name); // Name of the file mapping object

if (shared_file_handler) {
// map memory file view, get pointer to the shared memory
LPVOID lp_base = MapViewOfFile(
shared_file_handler, // Handle of the map object
FILE_MAP_ALL_ACCESS, // Read and write access
0, // High-order DWORD of the file offset
0, // Low-order DWORD of the file offset
buffer_size); // The number of bytes to map to view

// write width, height, channel to a memory
uchar* tmp = (uchar*) malloc(buffer_size);
memcpy(tmp, &width, sizeof(width));
tmp += sizeof(width);
memcpy(tmp, &height, sizeof(height));
tmp += sizeof(height);
memcpy(tmp, &channel, sizeof(channel));
tmp += sizeof(channel);
memcpy(tmp, img.data, data_size);

for (int i = 0; i < 10; i++) {
uchar val = tmp[i];
cout << (int)val << endl;
}

// copy data to shared memory
tmp -= sizeof(int) * 3;
memcpy(lp_base, tmp, buffer_size);

free(tmp);

FlushViewOfFile(lp_base, buffer_size); // can choose save to file or not

// process wait here for other task to read data
cout << "already write to shared memory, wait ..." << endl;

this_thread::sleep_for(chrono::seconds(6000));

// close shared memory file
UnmapViewOfFile(lp_base);
CloseHandle(shared_file_handler);
CloseHandle(hFile);
cout << "shared memory closed" << endl;
} else
cout << "create mapping file error" << endl;
}

int main(int argc, char* argv[]) {
char* imgDir = "C:/Users/taoxuan.G08/Documents/Visual Studio 2015/Projects/cnpy-solution/cv/vehicle.jpeg";
writeMemory(imgDir);

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// img_reader.cpp
#include <iostream>
#include <string>
#include <Windows.h>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>

using namespace std;
using namespace cv;

void readMemory() {
char *shared_object_name = "Local\\object_name_sean";

// open shared memory file
HANDLE shared_file_handler = OpenFileMapping(
FILE_MAP_ALL_ACCESS,
NULL,
shared_object_name);

if (shared_file_handler) {

LPVOID lp_base = MapViewOfFile(
shared_file_handler,
FILE_MAP_ALL_ACCESS,
0,
0,
0);

// copy shared data from memory
cout << "read shared data: " << endl;

uchar* tmp = (uchar*)lp_base;
int width;
int height;
int channel;
memcpy(&width, tmp, sizeof(width));
tmp += sizeof(int);
memcpy(&height, tmp, sizeof(height));
tmp += sizeof(int);
memcpy(&channel, tmp, sizeof(channel));
tmp += sizeof(int);

Mat img = Mat(height, width, CV_8UC3);
memcpy(img.data, tmp, height * width * channel);

namedWindow("test");
imshow("test", img);
waitKey(2);

// close share memory file
UnmapViewOfFile(lp_base);
CloseHandle(shared_file_handler);

} else
cout << "open mapping file error" << endl;
}

int main(int argc, char* argv[]) {
readMemory();

system("pause");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# img_reader.py
import cv2
import numpy as np
from ctypes import *


FILE_MAP_ALL_ACCESS = 0xF001F
szName = c_char_p('Local\object_name_sean')

hMapObject = windll.kernel32.OpenFileMappingA(FILE_MAP_ALL_ACCESS, False, szName)
print "Error after OpenFileMappingA ->", GetLastError() # If everything runs smoothly, GetLastError should return 0
if hMapObject == 0:
print 'Could not open file mapping object'
raise windll.WindowsError()

pBuf = windll.kernel32.MapViewOfFile(hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, 0)
print "Error after MapViewOfFile ->", GetLastError() # If everything runs smoothly, GetLastError should return 0

if pBuf == 0:
print 'Could not map view of file'
windll.kernel32.GetLastError()
else:
pBuf_int = cast(pBuf, POINTER(c_int))
width, height, channel = pBuf_int[:3]

data_len = width * height * channel
pBuf_ubyte = cast(pBuf, POINTER(c_ubyte))
data_ubyte = pBuf_ubyte[: data_len] # The pointer is already incremented by 12 bytes, not sure why.

image = np.asarray(data_ubyte, dtype=np.uint8)
image = image.reshape((height, width, channel))

cv2.imshow('python image', image)
cv2.waitKey()

windll.kernel32.UnmapViewOfFile(pBuf)
windll.kernel32.CloseHandle(hMapObject)

img_writer in C++ img_reader in C++ img_reader in python

不知为什么,python 版的 reader 用脚本执行正常,但是用 pyinstaller 打包成 exe 后就无法获取共享内存的指针,MapViewOfFile 始终返回 0,正在解决这个问题。

除此之外,发现 python 版中在执行完 OpenFileMappingAMapViewOfFile 后,分别调用 GetLastError 都应该返回 0 (0 表示没有错误)。C++ 版本的 reader 就是如此。但是 img_reader.py 用脚本方式执行却都返回 2,按理说返回 2 了就表示有错,应该无法正确读取共享内存数据,但是用脚本方式执行却可以读取图像数据。这一点很奇怪。而将 img_reader.py 转换成 exe 后,在 OpenFileMappingA 后调用 GetLastError 返回 2,MapViewOfFile 后返回 6,也无法正确读取图像数据。

由于现有的方法无法在转成 exe 后成功运行,考虑使用新的方法来读取共享内存。这里使用了 mmap 库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import mmap
import struct
import numpy as np
import cv2


shm = mmap.mmap(0, 720*480*3+12, "object_name_sean", mmap.ACCESS_READ)

print shm
if shm:
header = shm.read(12)
width_str = header[:4]
height_str = header[4:8]
channel_str = header[8:12]

width = struct.unpack('<i', width_str)[0]
height = struct.unpack('<i', height_str)[0]
channel = struct.unpack('<i', channel_str)[0]

print width, height, channel

data = shm.read(width * height * channel)

img_data = [ord(x) for x in data]
print img_data[:10]

image = np.asarray(img_data, dtype=np.uint8)
image = image.reshape((height, width, channel))

cv2.imshow('python image', image)
cv2.waitKey()

shm.close()

img reader in python mmap


文章作者: taosean
文章链接: https://taosean.github.io/2018/12/03/Inter-Process-Communication-by-Shared-Memory-on-Windows/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 taosean's 学习之旅