如何将 Python Console 窗口内嵌到 MFC 程序中?
如题,因为我想在 mfc 里边和 py 交互,而且交互的过程又有实时的结果显示,所以想比较理想的就是嵌入一个 python 的 console 进去。
自己实现了直接 createprocess 创建 cmd,但是同样的直接 createprocess python console 就卡死了。stdin 和 stdout 也没用。所以想干脆内嵌一个,不用 createprocess 了。
有好的想法可以提一提。
如何将 Python Console 窗口内嵌到 MFC 程序中?
什么交互?
要在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。关键点:
- 使用
Py_Initialize()初始化Python解释器 - 创建全局字典用于存储Python变量
- 用
PyRun_String()执行代码 - 捕获并显示输出和错误
简单说就是通过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 是核心

