PythonとC++の間でデータのやり取りをしたい場合があると思います。
単純なアプリならどちらかをDLL化してしまえば解決しますが、外部のAPIを使用していると片方には対応していなかったり、 処理が複雑でプロセスごとに切り分けたいなど、そういう場面ではプロセス間通信が必要になる場合があります。
ファイルを介してデータのやり取りを行う実装は簡単ですが、処理速度が遅いので今回は共有メモリを使用してC++⇔Python間のデータのやり取りを行う方法についてまとめました。
※共有メモリはOSの環境に依存するので、今回はWindows 11を想定しています。
1. C++のプロセス間通信
まずはC++側で共有メモリを使用したプロセス間通信を実装します。
1.1. Windows APIの使用
C++で共有メモリを使用するには、Windows APIの関数を呼び出す必要があります。
使用するAPIは以下の通りです。
HANDLE CreateFileMappingA(
HANDLE hFile,
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
LPCSTR lpName
);
| 引数 | 内容 |
|---|---|
| hFile | ファイルへのハンドルを指定します。 共有メモリにファイルを使用しない 場合は「INVALID_HANDLE_VALUE」 を指定します。 |
| lpFileMappingAttributes | セキュリティ属性を指定します。 アクセス制御リストをして できますが、デフォルトでいい 場合は「NULL」を指定します。 |
| flProtect | 保護属性を指定します。 PAGE_READONLY:読み取り専用 PAGE_READWRITE:読み取り/書き込み PAGE_WRITECOPY:書き込み専用 |
| dwMaximumSizeHigh | 最大サイズの上位32bit |
| dwMaximumSizeLow | 最大サイズの下位32bit |
| lpName | 共有メモリの名前 |
LPVOID MapViewOfFile(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap
);
| 引数 | 内容 |
|---|---|
| hFileMappingObject | ファイルマッピングオブジェクトのハンドル CreateFileMappingAの戻り値を指定 |
| dwDesiredAccess | アクセスの種類を指定します。 FILE_MAP_ALL_ACCESS:読み取り/書き込み FILE_MAP_READ:読み取り専用 FILE_MAP_WRITE:こちらも読み取りと 書き込み両方できるが FILE_MAP_ALL_ACCESSが 推奨されている |
| dwFileOffsetHigh | マッピング開始オフセットの上位32bit |
| dwFileOffsetLow | マッピング開始オフセットの32bit |
| dwNumberOfBytesToMap | ファイルマッピングのバイト数 |
HANDLE OpenFileMappingA(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCSTR lpName
);
| 引数 | 内容 |
|---|---|
| dwDesiredAccess | アクセスの種類を指定します。 FILE_MAP_ALL_ACCESS:読み取り/書き込み FILE_MAP_READ:読み取り専用 FILE_MAP_WRITE: こちらも読み取り/書き込み両方できるが FILE_MAP_ALL_ACCESSが推奨されている |
| bInheritHandle | プロセスのハンドルを子プロセスに 継承させたい場合は「TRUE」 デフォルトは「FALSE」 |
| lpName | 共有メモリの名前 |
BOOL UnmapViewOfFile(
LPCVOID lpBaseAddress
);
| 引数 | 内容 |
|---|---|
| lpBaseAddress | ファイルマッピングビューのポインタ MapViewOfFileの戻り値 |
BOOL CloseHandle(
HANDLE hObject
);
| 引数 | 内容 |
|---|---|
| hObject | オブジェクトのハンドル CreateFileMappingA(OpenFileMappingA)の戻り値 |
1.2. 送信側の実装
まずは送信側を実装します。
このコードでは先頭に送受信用のフラグ1Byte+データやり取り用の領域1024Byteで、先頭のフラグが1になったら受信側で共有メモリに書き込まれたデータを表示し、フラグを0に設定します。
送信側はフラグが0になったことを確認してデータのやり取りを完了します。
#include <iostream>
#include <windows.h>
#include <chrono>
#define D_SHARED_MEMORY_NAME "SHARED_MEMORY"
#define D_SHARED_MEMORY_SIZE 1025
#define D_TIME_OUT 3000 //タイムアウト(ms)
using namespace std;
int main(void) {
auto start = chrono::system_clock::now();
auto end = chrono::system_clock::now();
const char send_flg = 0x01;
const char reset_flg = 0x00;
// 共有メモリ作成
HANDLE hMapFile = CreateFileMappingA(
INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
D_SHARED_MEMORY_SIZE,
D_SHARED_MEMORY_NAME
);
if (hMapFile == NULL) {
cerr << "CreateFileMapping:Error\n";
return -1;
}
// ファイルマッピングのビューをアドレス空間にマッピング
LPVOID pBuf = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, D_SHARED_MEMORY_SIZE);
if (pBuf == NULL) {
cerr << "MapViewOfFile:Error\n";
CloseHandle(hMapFile);
return -1;
}
cout << "入力待ち" << endl;
string str_input;
while (1) {
cin >> str_input;
if (str_input == "!end") {
memset(((char*)pBuf) + 1, 0, D_SHARED_MEMORY_SIZE - 1);
strcpy_s(((char*)pBuf) + 1, str_input.length() + 1, str_input.c_str());
((char*)pBuf)[0] = 0x01;
break;
}
// 書き込み
int len = str_input.length();
if (len > D_SHARED_MEMORY_SIZE-1) {
cerr << "サイズ超過:" << len << "\n";
continue;
}
memset(((char*)pBuf) + 1, 0, D_SHARED_MEMORY_SIZE - 1);
strcpy_s(((char*)pBuf) + 1, str_input.length()+1, str_input.c_str());
((char*)pBuf)[0] = 0x01;
char check = 1;
double elapsed = 0.0;
start = chrono::system_clock::now();
while (1) {
Sleep(10);
if (elapsed > D_TIME_OUT) {
strcpy_s((char*)pBuf, D_SHARED_MEMORY_SIZE, &reset_flg);
cerr << "タイムアウト\n";
break;
}
if (((char*)pBuf)[0] == 0x00) break;
end = chrono::system_clock::now();
elapsed = chrono::duration_cast(end - start).count();
}
}
cout << "終了" << endl;
UnmapViewOfFile(pBuf);
CloseHandle(hMapFile);
return 0;
}
1.3. 受信側の実装
次に受信側を実装します。
#include <iostream>
#include <windows.h>
#include <chrono>
#define D_SHARED_MEMORY_NAME "SHARED_MEMORY"
#define D_SHARED_MEMORY_SIZE 1025
using namespace std;
int main(void) {
const char check_flg = 0x01;
const char reset_flg = 0x00;
HANDLE hMapFile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, D_SHARED_MEMORY_NAME);
if (hMapFile == NULL) {
std::cerr << "OpenFileMapping failed\n";
return 1;
}
// ファイルマッピングのビューをアドレス空間にマッピング
LPVOID pBuf = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, D_SHARED_MEMORY_SIZE);
if (pBuf == NULL) {
cerr << "MapViewOfFile:Error\n";
CloseHandle(hMapFile);
return -1;
}
string str_input;
while (1) {
Sleep(10);
if (((char*)pBuf)[0] == check_flg) {
char recv_data[D_SHARED_MEMORY_SIZE - 1] = {};
memcpy(recv_data, (char*)pBuf + 1, D_SHARED_MEMORY_SIZE - 1);
string message(recv_data, strnlen(recv_data, D_SHARED_MEMORY_SIZE - 1));
cout << message << endl;
memset((char*)pBuf + 1, 0, D_SHARED_MEMORY_SIZE - 1);
((char*)pBuf)[0] = reset_flg;
if (message == "!end") break;
}
}
cout << "終了" << endl;
UnmapViewOfFile(pBuf);
CloseHandle(hMapFile);
return 0;
}
1.4. 動作確認
実際に動作確認をしてみます。
画像のように、送信側で入力したデータを受信側で受け取ったことが分かります。

2. Pythonのプロセス間通信
次にPythonでプロセス間通信を実装してみます。
2.1. SharedMemoryの使用
Pythonで共有メモリを使用する方法はいくつかありますが、今回はこちらの関数を使用します。
| 引数 | 内容 |
|---|---|
| name | 共有メモリの名前 |
| create | 新規作成なら「True」 既存の共有メモリにアクセスする場合は「False」 |
| size | バッファサイズ(Byte) |
2.2. 送信側の実装
C++と同じように送信側を実装します。
import time
from multiprocessing import shared_memory
import time
D_SHARED_MEMORY_NAME = "SHARED_MEMORY"
D_SHARED_MEMORY_SIZE = 1025
mem = shared_memory.SharedMemory(name=D_SHARED_MEMORY_NAME, create=True, size=D_SHARED_MEMORY_SIZE)
while(True):
input_data = input("入力待ち")
data = input_data.encode("utf-8")
if input_data == "!end":
mem.buf[1:1+len(data)] = data
mem.buf[0] = 0x01
break
if len(data) > (D_SHARED_MEMORY_SIZE-1):
print("サイズ超過:"+len(data))
continue
mem.buf[1:1+len(data)] = data
mem.buf[0] = 0x01
start = time.time()
while(1):
time.sleep(0.01)
if mem.buf[0] == 0x00:
break
end = time.time()
if end-start > 3:
print("タイムアウト")
break
mem.close()
mem.unlink()
2.3. 受信側の実装
次に受信側を実装します。
from multiprocessing import shared_memory
D_SHARED_MEMORY_NAME = "SHARED_MEMORY"
D_SHARED_MEMORY_SIZE = 1025
mem = shared_memory.SharedMemory(name=D_SHARED_MEMORY_NAME)
# データ読み取り(バッファから先頭のデータを取得)
while(True):
read_data = bytes(mem.buf[:D_SHARED_MEMORY_SIZE])
if read_data[0] == 0x01:
read_data = bytes(mem.buf[:D_SHARED_MEMORY_SIZE])
message = read_data[1:].rstrip(b'\x00').decode('utf-8')
print("受信メッセージ:", message)
mem.buf[1:D_SHARED_MEMORY_SIZE] = b'\x00' * (D_SHARED_MEMORY_SIZE-1)
mem.buf[0] = 0x00
if message == "!end":
break
mem.close()
mem.unlink()
2.4. 動作確認
画像のように、データの送受信ができることが確認できました。

3. C++とPythonのプロセス間通信
今回の方法であれば、ソースコードを変更しなくてもC++とPythonのプロセス間通信が可能になります。
例えばC++側の送信用プログラムとPython側の受信用プログラムでも同じように動作します。
最後に、Python側でOpenCVで読み込んだ画像をC++側に送り、C++側で何か処理を加えてPython側に返して保存してみます。
※今回使用した画像はこちら

3.1. Python側実装(送信側)
まずはPython側を実装します。
画像を読み込んでから何かキーを押したら共有メモリにフラグと画像を送信し、フラグが0になったら再び共有メモリから画像を読みだして保存します。
import cv2
import numpy as np
from multiprocessing import shared_memory
import time
D_SHARED_MEMORY_NAME = "SHARED_MEMORY"
D_WIDTH = 445
D_HEIGHT = 368
img = cv2.imread('test.png', cv2.IMREAD_COLOR)
h = D_HEIGHT
w = D_WIDTH
c = 3
memsize = 1 + h * w * c
mem = shared_memory.SharedMemory(name=D_SHARED_MEMORY_NAME, create=True, size=memsize)
input("入力待ち")
mem.buf[1:memsize] = img.tobytes()
mem.buf[0] = 0x01
start = time.time()
while(1):
time.sleep(0.01)
if mem.buf[0] == 0x00:
img_recv = np.frombuffer(mem.buf[1:memsize], dtype=np.uint8).reshape((h, w, c))
break
end = time.time()
if end-start > 3:
print("タイムアウト")
break
cv2.imwrite("result.png", img_recv)
del img_recv
mem.close()
mem.unlink()
3.2 C++側実装(受信側)
次にC++側を実装します。
共有メモリから画像を読み込んだらcv::Matに変換し、正方形を描画して再び共有メモリに書き込みます。
#include <iostream>
#include <windows.h>
#include <opencv2/opencv.hpp<
#define D_SHARED_MEMORY_NAME "SHARED_MEMORY"
#define D_WIDTH 445
#define D_HEIGHT 368
#define D_SHARED_MEMORY_SIZE D_WIDTH*D_HEIGHT*3+1
using namespace std;
using namespace cv;
int main(void) {
const char check_flg = 0x01;
const char reset_flg = 0x00;
HANDLE hMapFile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, D_SHARED_MEMORY_NAME);
if (hMapFile == NULL) {
std::cerr << "OpenFileMapping failed\n";
return 1;
}
// ファイルマッピングのビューをアドレス空間にマッピング
LPVOID pBuf = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, D_SHARED_MEMORY_SIZE);
if (pBuf == NULL) {
cerr << "MapViewOfFile:Error\n";
CloseHandle(hMapFile);
return -1;
}
string str_input;
while (1) {
Sleep(10);
if (((char*)pBuf)[0] == check_flg) {
uchar* recv_data = new uchar[D_SHARED_MEMORY_SIZE - 1];
memcpy(recv_data, (char*)pBuf + 1, D_SHARED_MEMORY_SIZE - 1);
Mat mat_img(D_HEIGHT, D_WIDTH, CV_8UC3, recv_data);
rectangle(mat_img, cv::Rect(D_WIDTH / 2 - D_WIDTH / 4, D_HEIGHT / 2 - D_HEIGHT / 4, D_WIDTH / 2, D_HEIGHT / 2), cv::Scalar(0, 0, 255), 2);
memcpy((char*)pBuf + 1, mat_img.data, D_SHARED_MEMORY_SIZE - 1);
delete[] recv_data;
((char*)pBuf)[0] = reset_flg;
break;
}
}
cout << "終了" << endl;
UnmapViewOfFile(pBuf);
CloseHandle(hMapFile);
return 0;
}
3.3. 動作確認
結果は以下の通りです。
Pythonで読み込んだ画像がC++側で正方形が描画されて返ってきました。

今回は以上です。
4. 参考文献・参考サイト
・CreateFileMappingA 関数 (winbase.h)
https://learn.microsoft.com/ja-jp/windows/win32/api/winbase/nf-winbase-createfilemappinga
・CreateFileMapping(A)
https://chokuto.ifdef.jp/advanced/function/CreateFileMapping.html
・MapViewOfFile 関数 (memoryapi.h)
https://learn.microsoft.com/ja-jp/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile
・MapViewOfFile
https://chokuto.ifdef.jp/urawaza/api/MapViewOfFile.html
・OpenFileMappingA 関数 (winbase.h)
https://learn.microsoft.com/ja-jp/windows/win32/api/winbase/nf-winbase-openfilemappinga
・OpenFileMapping
https://www.tokovalue.jp/function/OpenFileMapping.htm
・UnmapViewOfFile 関数 (memoryapi.h)
https://learn.microsoft.com/ja-jp/windows/win32/api/memoryapi/nf-memoryapi-unmapviewoffile
・CloseHandle 関数 (handleapi.h)
https://learn.microsoft.com/ja-jp/windows/win32/api/handleapi/nf-handleapi-closehandle
・C++でフリープラットフォームな時間計測
https://qiita.com/yukiB/items/01f8e276d906bf443356
・multiprocessing.shared_memory — プロセス間で直接アクセス可能な共有メモリ
https://docs.python.org/ja/3.12/library/multiprocessing.shared_memory.html



コメント