Python GUI与C++模块交互:实现向C++的std::cin输出及替代方案咨询
Hey there! Let's break down why your current setup isn't working and walk through the best solutions to get your Python GUI sending data to your C++ module properly.
First, the core issue with your existing code
Your C++ program spins up a Python GUI thread via PyRun_SimpleFile, but then your Python GUI uses Popen(['test'], ...) to launch a brand new instance of your C++ executable. That means when you write to p.stdin, you're sending data to this new process—not the original C++ process that started the GUI. No wonder your std::cin in the main C++ loop isn't getting anything!
Let's fix this with the most straightforward approaches:
Solution 1: Use Python Embedding API Directly (Best for Your Setup)
Since your Python GUI is running inside the same process as your C++ code (you're embedding Python in C++), you don't need pipes or subprocesses at all. You can expose a C++ function to Python that lets the GUI send data directly to your C++ logic. This is efficient and avoids inter-process communication headaches.
Modified C++ Code
#include <fstream> #include <string> #include <stdio.h> #include <Python.h> #include <thread> #include <mutex> #include <condition_variable> using namespace std; // Thread-safe variables to hold GUI input std::string gui_input; std::mutex input_mutex; std::condition_variable input_cv; bool has_new_input = false; bool gui_is_alive = true; // C++ function exposed to Python—this is how the GUI sends data static PyObject* set_gui_input(PyObject* self, PyObject* args) { const char* input_str; if (!PyArg_ParseTuple(args, "s", &input_str)) { return nullptr; } // Lock to safely update shared data std::lock_guard<std::mutex> lock(input_mutex); gui_input = input_str; has_new_input = true; input_cv.notify_one(); // Wake up the main thread to process the input return PyLong_FromLong(0); } // Define the Python module and its methods static PyMethodDef GuiCommMethods[] = { {"set_gui_input", set_gui_input, METH_VARARGS, "Receive input from Python GUI"}, {nullptr, nullptr, 0, nullptr} // Sentinel value }; static struct PyModuleDef gui_comm_module = { PyModuleDef_HEAD_INIT, "gui_comm", // Module name Python will import nullptr, -1, GuiCommMethods }; // Initialize the module for Python PyMODINIT_FUNC PyInit_gui_comm(void) { return PyModule_Create(&gui_comm_module); } void launch_gui() { Py_Initialize(); PyInit_gui_comm(); // Register our custom module FILE* gui_script = fopen("gui.py", "r"); if (gui_script) { PyRun_SimpleFile(gui_script, "gui.py"); fclose(gui_script); } Py_Finalize(); gui_is_alive = false; input_cv.notify_one(); // Tell main thread GUI has closed } int main() { std::thread gui_thread(launch_gui); while (gui_is_alive) { std::unique_lock<std::mutex> lock(input_mutex); // Wait for new input or GUI shutdown input_cv.wait(lock, []{ return has_new_input || !gui_is_alive; }); if (has_new_input) { std::cout << "Received from GUI: " << gui_input << std::endl; has_new_input = false; // Reset for next input } } gui_thread.join(); return 0; }
Modified Python GUI Code
from tkinter import Tk, Scale import gui_comm # Import our custom C++-exposed module root = Tk() root.title('GUI') root.geometry("800x600") root.minsize('800','600') root.maxsize('800','600') def on_slider_change(value): print(f"From PythonGUI: send {value}") # Call the C++ function directly to pass the slider value gui_comm.set_gui_input(value) slider = Scale( root, orient=HORIZONTAL, sliderlength=25, from_=0, to=100, resolution=1, label="Value", font=("Ubuntu", 11), length=500, command=on_slider_change ) slider.pack(side=TOP, anchor="w") root.mainloop()
Compilation Note
When compiling the C++ code, link against your Python library (adjust the version for your system):
g++ your_file.cpp -o test -std=c++11 -I/usr/include/python3.10 -lpython3.10
Solution 2: Inter-Process Pipes (If You Need Separate Processes)
If you want the Python GUI to run as a separate process (instead of embedded), you can use pipes to connect the two. The idea is for the C++ process to create a pipe, launch the Python GUI, and have the GUI write to the pipe.
C++ Code (Pipe Version)
#include <iostream> #include <string> #include <unistd.h> #include <sys/wait.h> using namespace std; int main() { int pipefd[2]; if (pipe(pipefd) == -1) { perror("pipe failed"); return 1; } pid_t pid = fork(); if (pid == -1) { perror("fork failed"); return 1; } if (pid == 0) { // Child process: Launch Python GUI, pass pipe write FD as argument close(pipefd[0]); // Close unused read end char fd_str[10]; snprintf(fd_str, sizeof(fd_str), "%d", pipefd[1]); execlp("python3", "python3", "gui.py", fd_str, nullptr); perror("execlp failed"); return 1; } else { // Parent process: Read from pipe close(pipefd[1]); // Close unused write end char buffer[256]; ssize_t bytes_read; while ((bytes_read = read(pipefd[0], buffer, sizeof(buffer)-1)) > 0) { buffer[bytes_read] = '\0'; cout << "Received from GUI: " << buffer << endl; } wait(nullptr); close(pipefd[0]); } return 0; }
Python GUI Code (Pipe Version)
from tkinter import Tk, Scale import sys import os root = Tk() root.title('GUI') root.geometry("800x600") root.minsize('800','600') root.maxsize('800','600') # Get pipe write file descriptor from command line pipe_fd = int(sys.argv[1]) pipe = os.fdopen(pipe_fd, 'w') def on_slider_change(value): print(f"From PythonGUI: send {value}") pipe.write(f"{value}\n") pipe.flush() # Force data to be sent immediately slider = Scale( root, orient=HORIZONTAL, sliderlength=25, from_=0, to=100, resolution=1, label="Value", font=("Ubuntu", 11), length=500, command=on_slider_change ) slider.pack(side=TOP, anchor="w") def on_close(): pipe.close() root.destroy() root.protocol("WM_DELETE_WINDOW", on_close) root.mainloop()
Other Options to Consider
- Shared Memory: Use C++'s
<shared_memory>(C++17+) or Boost.Interprocess, paired with Python'smmapmodule, for high-speed large data transfers. - Local Sockets: Use Unix domain sockets (for Linux/macOS) or named pipes (Windows) for more complex bidirectional communication.
Final Recommendation
Go with Solution 1—since you're already embedding Python in your C++ module, direct API calls are the simplest, fastest, and most maintainable approach. Your original Popen usage was creating a separate process, which is why your data wasn't reaching the intended std::cin.
内容的提问来源于stack exchange,提问作者Pedro Mendes

