本文主要记载了在 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 int width, height;uchar* tmp = (uchar*) malloc (buffer_size); memcpy (tmp, &width, sizeof (width)) tmp += sizeof (width); 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 #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;void writeMemory (char * imgDir) { 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 ; HANDLE hFile = CreateFile(shared_file_name, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL , OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); if (hFile == INVALID_HANDLE_VALUE) cout << "create file error" << endl ; HANDLE shared_file_handler = CreateFileMapping( hFile, NULL , PAGE_READWRITE, 0 , buffer_size, shared_object_name); if (shared_file_handler) { LPVOID lp_base = MapViewOfFile( shared_file_handler, FILE_MAP_ALL_ACCESS, 0 , 0 , buffer_size); 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 ; } tmp -= sizeof (int ) * 3 ; memcpy (lp_base, tmp, buffer_size); free (tmp); FlushViewOfFile(lp_base, buffer_size); cout << "already write to shared memory, wait ..." << endl ; this_thread::sleep_for(chrono::seconds(6000 )); 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 #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" ; 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 ); 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 ); 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 import cv2import numpy as npfrom 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 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 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] 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 版中在执行完 OpenFileMappingA
和 MapViewOfFile
后,分别调用 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 mmapimport structimport numpy as npimport cv2shm = mmap.mmap(0 , 720 *480 *3 +12 , "object_name_sean" , mmap.ACCESS_READ) print shmif 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