如何将 Python Console 窗口内嵌到 MFC 程序中?

如题,因为我想在 mfc 里边和 py 交互,而且交互的过程又有实时的结果显示,所以想比较理想的就是嵌入一个 python 的 console 进去。
自己实现了直接 createprocess 创建 cmd,但是同样的直接 createprocess python console 就卡死了。stdin 和 stdout 也没用。所以想干脆内嵌一个,不用 createprocess 了。
有好的想法可以提一提。
如何将 Python Console 窗口内嵌到 MFC 程序中?

19 回复

什么交互?


要在MFC程序中内嵌Python控制台,可以使用Python的C API和MFC的CEdit控件。这里提供一个完整的示例:

// 在MFC对话框类中
#include <Python.h>

class CPythonConsoleDlg : public CDialogEx
{
    // ... 其他代码
    
protected:
    CEdit m_editConsole;
    PyObject* m_pGlobalDict;
    
public:
    virtual BOOL OnInitDialog()
    {
        CDialogEx::OnInitDialog();
        
        // 初始化Python
        Py_Initialize();
        m_pGlobalDict = PyDict_New();
        PyDict_SetItemString(m_pGlobalDict, "__builtins__", PyEval_GetBuiltins());
        
        // 创建控制台编辑框
        m_editConsole.Create(WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | 
                           ES_AUTOVSCROLL | ES_READONLY, 
                           CRect(10, 10, 400, 300), this, IDC_CONSOLE_EDIT);
        
        // 设置字体
        CFont font;
        font.CreatePointFont(100, _T("Courier New"));
        m_editConsole.SetFont(&font);
        
        return TRUE;
    }
    
    void ExecutePythonCode(const CString& code)
    {
        CStringA codeA(code);
        
        // 执行Python代码
        PyObject* pResult = PyRun_String(codeA, Py_single_input, 
                                        m_pGlobalDict, m_pGlobalDict);
        
        CString output;
        if (pResult)
        {
            PyObject* pStr = PyObject_Str(pResult);
            if (pStr)
            {
                output = PyUnicode_AsWideCharString(pStr, NULL);
                Py_DECREF(pStr);
            }
            Py_DECREF(pResult);
        }
        else
        {
            PyErr_Print();
            PyObject* pType, *pValue, *pTraceback;
            PyErr_Fetch(&pType, &pValue, &pTraceback);
            
            if (pValue)
            {
                PyObject* pStr = PyObject_Str(pValue);
                if (pStr)
                {
                    output = PyUnicode_AsWideCharString(pStr, NULL);
                    Py_DECREF(pStr);
                }
            }
        }
        
        // 显示输出
        CString currentText;
        m_editConsole.GetWindowText(currentText);
        currentText += _T(">>> ") + code + _T("\r\n");
        if (!output.IsEmpty())
        {
            currentText += output + _T("\r\n");
        }
        m_editConsole.SetWindowText(currentText);
        m_editConsole.LineScroll(m_editConsole.GetLineCount());
    }
    
    virtual void OnDestroy()
    {
        Py_DECREF(m_pGlobalDict);
        Py_Finalize();
        CDialogEx::OnDestroy();
    }
};

还需要添加一个编辑框用于输入命令,并在按下回车时调用ExecutePythonCode。关键点:

  1. 使用Py_Initialize()初始化Python解释器
  2. 创建全局字典用于存储Python变量
  3. PyRun_String()执行代码
  4. 捕获并显示输出和错误

简单说就是通过Python C API把解释器集成到MFC里。

就是一条条的输入命令,然后一条条输出结果。

你需要实现的是 Pseudo Console,但是在 Win10 1809 才正式支持 Pseudo Console API。
要在旧的系统中实现的话:
要不然参照 ConEmu 去读取 Console 的内容。
要不然直接将程序的 stdin 和 stdout 重定向到管道。
不过管道这个方法可能会有问题,因为有的程序会调用 Console API 进行一些操作,重定向到管道之后这些操作会失败。

我觉得更好的做法是通过别的途径进行交互,而不要使用 Console。

还有这种操作? 看来是我太 low 了

现在还用 mfc 的吗,我八年前学的时候就是老东西了,就是觉得复杂

你想要的东西应该是 REPL

首先你要会写一个 Shell {
Shell 的本质是<br> 从 stdin 按行读取用户输入
解析<br> 做出反应
回到第一步<br>}<br><br>然后再在 Shell 基础上实现 REPL<br><br>如何写一个 Python 的 REPL {<br> 可以参考 Python 的官方[开发]文档 和 Python.h 中的定义
举个栗子<br> PyObject *pModule,*pFunc;
PyObject *pArgs, *pValue;<br>
pModule = PyImport_Import(PyString_FromString("random")); //import random<br>
pFunc = PyObject_GetAttrString(pModule, "random"); //获取 random.random<br>
pArgs = NULL;<br>
pValue = PyObject_CallObject(pFunc, pArgs); //调用 random.random()<br>
res = PyInt_AsLong(pValue); //res 即是 random.random() 的返回值<br>
按照上边的 <br> 如果用户输出了 import string 那么就应该执行 pModule = PyImport_Import(PyString_FromString(“string”));
}

其余的就算逻辑问题 就不多说了

V 站怎么还吞空格的

试试 PythonQt 我前一个月用过 和你的需求一模一样

直接将命令行的黑框的父窗体设置为 MFC 窗体,这个简单方便

用 PyQt 写吧 MFC 已经凉了很久了

大佬~

统一回复:感谢楼上各位大佬。

不需要代码提示的话人工读入,然后按行 eval 就行。有更高的要求应该可以考虑从 ipython notebook 相关内容着手。

没那么复杂,我就搞过 mfc 嵌入 putty.exe ,代码如下:
HANDLE hProcess = CreateProcess(…); // 用 cmd.exe /K python.exe 启动
if (NULL == hProcess)
{
CMessageBox::Show(this, _T(“FAILED TO START PUTTY”),
CTextTraits::EMPTY_STRING, IDOK, MB_OK);
return FALSE;
}
if (0 == ::WaitForInputIdle(hProcess, 1000))
{
m_wndTelnet = GetProcessMainWnd((DWORD)iProcessID);
}
CWnd *pWndPos = GetDlgItem(IDC_STATIC_POSITION); //嵌入的位置标识 ID
if (NULL == pWndPos)
{
return FALSE;
}
CRect rcPos;
pWndPos->GetWindowRect(&rcPos);
ScreenToClient(&rcPos);
rcPos.DeflateRect(1, 12, 1, 1);
::SetParent(m_wndTelnet, m_hWnd); // 窗口句柄 m_wndTelnet.
::SetWindowPos(m_wndTelnet, NULL, rcPos.left, rcPos.top
, rcPos.Width(), rcPos.Height(), SWP_SHOWWINDOW);
LONG lRet = ::SetWindowLong(m_wndTelnet, GWL_STYLE, 0x156B0000); // 这个魔术数字我也忘记啥意思了。
直接就能在 cmd 里面输入参数了。。。。
我还在外面搞了参数表格,拦截按钮事件把参数组好了往 m_wndTelnet 塞,
::SetForegroundWindow(hwndTelnet);
for (int i = 0; i < strMsg.GetLength(); ++i)
{
TCHAR chKey = strMsg.GetAt(i);
SHORT shTmp = VkKeyScan(chKey);
BYTE byVKey = shTmp & 0xFF; // key
bool bShift = (shTmp & 0x0100) == 0x0100 ? true : false;

if (bShift)
{
keybd_event(VK_SHIFT, MapVirtualKey(VK_SHIFT, MAPVK_VK_TO_VSC), 0, 0);
}

keybd_event(byVKey, MapVirtualKey(byVKey, MAPVK_VK_TO_VSC), 0, 0);
keybd_event(byVKey, MapVirtualKey(byVKey, MAPVK_VK_TO_VSC), KEYEVENTF_KEYUP, 0);

if (bShift)
{
keybd_event(VK_SHIFT, MapVirtualKey(VK_SHIFT, MAPVK_VK_TO_VSC), KEYEVENTF_KEYUP, 0);
}
}
Sleep(100);
keybd_event(VK_RETURN, MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC), 0, 0);
keybd_event(VK_RETURN, MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC), KEYEVENTF_KEYUP, 0);

后来我用 Boost.python 自己弄 C++/PY 互相调用玩,
搭建了一个简单的图形学绘制框架,支持用脚本绘制 D2D,由于太懒就没搞很复杂。

lz 可以参考里面互相调用的部分,自己做个 repl 编辑器,然后 cpp/py 互相传数据。

https://github.com/Liudx1985/D2DGraph/tree/master/DrawGraph

就那句 setparent 是核心

回到顶部