使用子进程实现 C++ 与 Python 交互式控制台

引言​

在软件开发中,有时我们需要在C++应用程序中嵌入Python解释器,实现两种语言间的交互操作。本文介绍一种基于 ​​Windows 匿名管道​​的技术方案,通过重定向标准输入/输出实现 C++ 对 Python 解释器的实时控制。核心代码仅 200 行,却能构建完整的交互式控制台。

一、技术核心:匿名管道与进程通信​

Windows 管道(CreatePipe)是进程间通信的关键设施。我们通过以下步骤建立通信通道:

  1. 创建双向管道
cpp 复制代码
CreatePipe(&hChildStd_OUT_Rd_, &hChildStd_OUT_Wr_, &saAttr, 0);
SetHandleInformation(hChildStd_OUT_Rd_, HANDLE_FLAG_INHERIT, 0);
  • 输出管道:子进程(Python)写 → 父进程(C++)读;
  • 输入管道:父进程写 → 子进程读;
  • SetHandleInformation 确保父进程独享管道控制权。
  1. 重定向 Python 的标准流
    STARTUPINFO 中配置标准流重定向:
cpp 复制代码
STARTUPINFO siStartInfo;
siStartInfo.hStdInput = hChildStd_IN_Rd_;   // Python 从此读命令
siStartInfo.hStdOutput = hChildStd_OUT_Wr_;  // Python 向此写结果
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

二、核心类 ScriptInteractor 剖析​

2.1 进程启动与管道绑定​

通过 CreateProcess 启动 Python 交互环境:

cpp 复制代码
void start_process() {
    wchar_t cmdLine[] = L"python.exe -i";  // 启动交互式解释器
    CreateProcess(NULL, cmdLine, ..., &siStartInfo, &piProcInfo);
    CloseHandle(hChildStd_OUT_Wr_);  // 父进程无需写端
    CloseHandle(hChildStd_IN_Rd_);   // 父进程无需读端
}

关键点:

  • CREATE_NO_WINDOW 隐藏控制台窗口;
  • 关闭冗余句柄防止资源泄漏。

2.2 异步输出捕获线程

使用独立线程实时捕获Python输出:

cpp 复制代码
void read_output() {
    while (running_.load()) {
        PeekNamedPipe(hChildStd_OUT_Rd_, ..., &dwAvailable, ...);
        if (dwAvailable > 0) {
            ReadFile(hChildStd_OUT_Rd_, chBuf, ...);
            std::cout << chBuf;  // 实时打印Python输出
        }
        std::this_thread::sleep_for(50ms);
    }
}
  • PeekNamedPipe 非阻塞检查数据,避免线程死锁;
  • 原子标志 running_实现安全线程退出。

2.3 命令发送与退出控制​

cpp 复制代码
void send_script(const std::string& data) {
    WriteFile(hChildStd_IN_Wr_, data.c_str(), data.size(), ...);
}
void quit() {
    send_script("exit()\n");  // 发送Python退出指令
    running_ = false;         // 停止输出线程
}

三、主循环:交互式控制台实现​

cpp 复制代码
int main() {
    SetConsoleOutputCP(CP_UTF8);  // 支持中文输出
    ScriptInteractor interactor;
    while (true) {
        std::string line;
        std::getline(std::cin, line);
        if (line == "exit()") break;
        interactor.send_script(line + "\n");  // 发送命令
    }
    interactor.quit();  // 优雅退出
}
  • 用户输入直接转发至 Python;
  • 退出时自动清理管道和线程资源。

四、完整源码

ScriptInteractor.hpp

cpp 复制代码
#include <string>
#include <thread>
#include <atomic>
#include <mutex>
#include <iostream>
#include <windows.h>

class ScriptInteractor {
public:
	ScriptInteractor() :hProcess_{}, hChildStd_IN_Rd_{}, hChildStd_IN_Wr_{}, hChildStd_OUT_Rd_{}, hChildStd_OUT_Wr_{} {
		create_pipes();
		start_process();

		// 启动输出读取线程
		running_.store(true, std::memory_order_release);
		outputThread_ = std::thread(&ScriptInteractor::read_output, this);
	}

	~ScriptInteractor() {
		// 通知线程退出并等待线程结束
		running_.store(false, std::memory_order_release);
		if (outputThread_.joinable()) {
			outputThread_.join();
		}

		// 关闭所有句柄
		close_handles();
	}

	void send_script(const std::string& data) {
		DWORD dwWritten;
		WriteFile(hChildStd_IN_Wr_, data.c_str(), static_cast<DWORD>(data.size()), &dwWritten, NULL);
	}

	void quit() {
		running_.store(false, std::memory_order_release);
		// 发送退出命令
		send_script("exit()\n");
	}

private:
	// 创建管道
	void create_pipes() {
		SECURITY_ATTRIBUTES saAttr{};
		saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
		saAttr.bInheritHandle = TRUE;
		saAttr.lpSecurityDescriptor = NULL;

		// 创建输出管道
		if (!CreatePipe(&hChildStd_OUT_Rd_, &hChildStd_OUT_Wr_, &saAttr, 0)) {
			std::cerr << "CreatePipe failed: " << GetLastError() << std::endl;
			return;
		}

		// 确保读取端不被继承
		if (!SetHandleInformation(hChildStd_OUT_Rd_, HANDLE_FLAG_INHERIT, 0)) {
			std::cerr << "SetHandleInformation failed: " << GetLastError() << std::endl;
			return;
		}

		// 创建输入管道
		if (!CreatePipe(&hChildStd_IN_Rd_, &hChildStd_IN_Wr_, &saAttr, 0)) {
			std::cerr << "CreatePipe failed: " << GetLastError() << std::endl;
			return;
		}

		// 确保写入端不被继承
		if (!SetHandleInformation(hChildStd_IN_Wr_, HANDLE_FLAG_INHERIT, 0)) {
			std::cerr << "SetHandleInformation failed: " << GetLastError() << std::endl;
			return;
		}
	}

	// 启动进程
	void start_process() {
		PROCESS_INFORMATION piProcInfo;
		STARTUPINFO siStartInfo;

		ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
		ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));

		siStartInfo.cb = sizeof(STARTUPINFO);
		siStartInfo.hStdError = hChildStd_OUT_Wr_;
		siStartInfo.hStdOutput = hChildStd_OUT_Wr_;
		siStartInfo.hStdInput = hChildStd_IN_Rd_;
		siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

		// 创建Python进程
		wchar_t cmdLine[] = L"python.exe -i";
		BOOL bSuccess = CreateProcess(
			NULL,           // 应用程序名
			cmdLine,        // 命令行
			NULL,           // 进程安全属性
			NULL,           // 线程安全属性
			TRUE,           // 继承句柄
			CREATE_NO_WINDOW, // 创建标志
			NULL,           // 环境变量
			NULL,           // 当前目录
			&siStartInfo,   // STARTUPINFO
			&piProcInfo     // PROCESS_INFORMATION
		);

		if (!bSuccess) {
			std::cerr << "CreateProcess failed: " << GetLastError() << std::endl;
			return;
		}

		// 保存进程句柄
		hProcess_ = piProcInfo.hProcess;

		// 关闭不需要的句柄
		CloseHandle(piProcInfo.hThread);
		CloseHandle(hChildStd_OUT_Wr_);
		CloseHandle(hChildStd_IN_Rd_);
	}

	void read_output() {
		constexpr int BUFFER_SIZE = 4096;
		DWORD dwRead;
		CHAR chBuf[BUFFER_SIZE]{};

		while (running_.load(std::memory_order_acquire)) {
			// 检查是否有数据可读
			DWORD dwAvailable;
			if (!PeekNamedPipe(hChildStd_OUT_Rd_, NULL, 0, NULL, &dwAvailable, NULL) || dwAvailable == 0) {
				std::this_thread::sleep_for(std::chrono::milliseconds(50));
				continue;
			}

			// 读取数据
			BOOL bSuccess = ReadFile(hChildStd_OUT_Rd_, chBuf, BUFFER_SIZE - 1, &dwRead, NULL);

			if (!bSuccess || dwRead == 0) {
				if (GetLastError() == ERROR_BROKEN_PIPE) {
					running_.store(false, std::memory_order_release);
					break;
				}
				continue;
			}

			// 处理读取到的数据
			chBuf[dwRead] = '\0';
			std::cout << chBuf;
		}
	}

	void close_handles() {
		if (hProcess_) {
			CloseHandle(hProcess_);
		}
		if (hChildStd_IN_Wr_) {
			CloseHandle(hChildStd_IN_Wr_);
		}
		if (hChildStd_OUT_Rd_) {
			CloseHandle(hChildStd_OUT_Rd_);
		}
	}

private:
	// Windows API句柄
	HANDLE hProcess_;
	HANDLE hChildStd_IN_Rd_;
	HANDLE hChildStd_IN_Wr_;
	HANDLE hChildStd_OUT_Rd_;
	HANDLE hChildStd_OUT_Wr_;

	// 线程管理
	std::thread outputThread_;
	std::atomic_bool running_;
};

main.cpp

cpp 复制代码
#include "ScriptInteractor.hpp"

int main() {
	// 设置控制台编码为UTF-8
	SetConsoleOutputCP(CP_UTF8);

	ScriptInteractor interactor;
	std::cout << "Python Interactive Console (Windows API)" << std::endl;
	std::cout << ">>> ";

	while (true) {
		std::string line;
		std::getline(std::cin, line);
		if (line == "exit()" || line == "quit()") {
			break;
		}

		// 发送命令给Python进程
		interactor.send_script(line + "\n");
	}

	interactor.quit();
	return 0;
}

五、跨平台解决方案

5.1 Boost.Process 实现方案

Boost.Process是Boost库中用于跨平台进程管理的组件,提供了统一的API来处理不同操作系统的进程管理差异。
核心实现思路:​

cpp 复制代码
#include <boost/process.hpp>
#include <boost/process/async.hpp>
#include <boost/asio.hpp>

namespace bp = boost::process;

class BoostScriptInteractor {
public:
    BoostScriptInteractor() : io_context_(), outputThread_(), running_(true) {
        create_process();
        start_async_read();
    }

    void send_script(const std::string& data) {
        if (stdin_) {
            stdin_->write(data.c_str(), data.size());
            stdin_->flush();
        }
    }

    void quit() {
        running_ = false;
        send_script("exit()\n");
        if (child_) {
            child_->terminate();
            child_->wait();
        }
    }

private:
    void create_process() {
        // 创建管道
        stdin_ = std::make_unique<bp::opstream>();
        stdout_ = std::make_unique<bp::ipstream>();
        
        // 启动Python进程
        child_ = std::make_unique<bp::child>(
            "python -i", // 命令行
            bp::std_in < *stdin_,
            bp::std_out > *stdout_,
            io_context_
        );
    }

    void start_async_read() {
        outputThread_ = std::thread([this]() {
            std::string line;
            while (running_) {
                if (stdout_ && std::getline(*stdout_, line)) {
                    std::cout << line << std::endl;
                } else {
                    std::this_thread::sleep_for(std::chrono::milliseconds(50));
                }
            }
        });
    }

private:
    boost::asio::io_context io_context_;
    std::unique_ptr<bp::child> child_;
    std::unique_ptr<bp::opstream> stdin_;
    std::unique_ptr<bp::ipstream> stdout_;
    std::thread outputThread_;
    std::atomic_bool running_;
};

5.2 Qt QProcess 实现方案

Qt 的 QProcess 类提供了强大的跨平台进程管理能力,特别适合GUI应用程序。
核心实现思路:

cpp 复制代码
#include <QProcess>
#include <QThread>

class QtScriptInteractor : public QObject {
    Q_OBJECT
public:
    QtScriptInteractor(QObject *parent = nullptr) : QObject(parent) {
        process_.setProgram("python");
        process_.setArguments(QStringList() << "-i");
        
        // 连接信号槽
        connect(&process_, &QProcess::readyReadStandardOutput, 
                this, &QtScriptInteractor::onReadyRead);
        connect(&process_, &QProcess::readyReadStandardError, 
                this, &QtScriptInteractor::onReadyRead);
        
        // 启动进程
        process_.start();
        process_.waitForStarted();
    }

    void send_script(const std::string& data) {
        if (process_.state() == QProcess::Running) {
            process_.write(data.c_str(), data.size());
        }
    }

    void quit() {
        send_script("exit()\n");
        process_.closeWriteChannel();
        process_.waitForFinished();
    }

public slots:
    void onReadyRead() {
        QByteArray output = process_.readAllStandardOutput();
        QByteArray error = process_.readAllStandardError();
        
        if (!output.isEmpty()) {
            std::cout << output.constData();
        }
        if (!error.isEmpty()) {
            std::cerr << error.constData();
        }
    }

private:
    QProcess process_;
};
相关推荐
码界筑梦坊5 小时前
74-基于Python的蜜雪冰城门店数据可视化分析系统
python·数据分析·flask·毕业设计
喜欢吃燃面5 小时前
算法中的链表结构
开发语言·c++·学习·算法
十五年专注C++开发5 小时前
Fruit框架:C++依赖注入解决方案
开发语言·c++·依赖注入·fruit框架
风亦辰7395 小时前
从 Hello World 到游戏世界——pygame 快速入门
python·游戏·pygame
Juan_20125 小时前
P1041题解
c++·算法·题解·搜索
冷崖5 小时前
const 与 constexpr
c++·学习
许泽宇的技术分享5 小时前
Windows MCP.Net:解锁AI助手的Windows桌面自动化潜能
人工智能·windows·.net·mcp
枫叶丹46 小时前
【Qt开发】多元素类控件(三)-> QTreeWidget
开发语言·数据库·c++·qt
hansang_IR6 小时前
【题解】P2217 [HAOI2007] 分割矩阵 [记忆化搜索]
c++·数学·算法·记忆化搜索·深搜