C 调用 Python
本文简单的讲解下C++如何生成一个解释器,调用python 的py脚本,采用的是python 用C封装好的C API。
Python 之父说,这些API 直接引用Python.h就可以使用了,即使它使用C编译的,因为在封装的时候就已经用extern “C” 描述过了。
其实说到底,低级语言调用高级语言,这个技术在嵌入式用的挺多的。但是可移植性却太差了(事实证明你需要在你的新的机器上安装python 和必要的第三方库,而且版本还要对得上。。。。。)
本文讲一下windows 上的配置以及编写使用方法。
具体的参考说明是python 官方的使用文档,在C API 那一章里。官方文档在你的python的安装目录里就有。
首先,我们进一下python 的安装目录:
我的是windows:
那仔细看一下:
我们用的python一般是用C 编写封装的,这大家都知道。
我先介绍下文件夹的说明,
libs 文件夹下就是python 的静态库,
include文件夹下这一大坨头文件,就是python 标准库的头文件。
至于DLL,仔细看下python 根目录下的文件
有python3.dll python39.dll,看到了吧,对我的python是3.9.0,64位,至于我为什么会把版本以及位指出来,后面很重要。
那么接下来就是新建C++ 工程了。
新建工程:
你是64位的python就把工程设为x64,我一般喜欢release ,因为相比于debug很轻量。
把之前讲的python根目录下的include libs 两个文件夹放在你的工程下(事实上不同版本的vs,工程文件的父子关系还是不一样的)
这是属性界面:(假设你已经懂得了C++ 工程属性各项的配置原理与方法)
这是源代码:
#include <Python.h>
#include <direct.h>
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
char cwd[256];
_getcwd(cwd, 256);
cout << cwd << endl;
string s = cwd;
Py_Initialize();
// 2、初始化python系统文件路径,保证可以访问到 .py文件
PyRun_SimpleString("import sys");
string sss = "sys.path.append('";
string ttt = "')";
string final = sss + cwd + ttt;
//PyRun_SimpleString("sys.path.append('D:/Python/CPP_INVOKE_PYTHON_2/CPP_INVOKE_PYTHON_2')");
replace(final.begin(), final.end(), '\\', '/');//将\\替换为/
const char* final_1 = final.c_str();
PyRun_SimpleString(final_1);
// 3、调用python文件名,不用写后缀
PyObject* pModule = PyImport_ImportModule("matrix");
if (pModule == NULL) {
cout << "module not found" << endl;
return 1;
}
// 4、调用函数
PyObject* pFunc = PyObject_GetAttrString(pModule, "Zimu");
if (!pFunc || !PyCallable_Check(pFunc)) {
cout << "not found function add_num" << endl;
return 0;
}
//
PyObject_CallObject(pFunc, NULL);
// 5、结束python接口初始化
Py_Finalize();
system("pause");
return 0;
}
然后还有一个python脚本,这是一个pygame模仿黑客帝国数字雨的东西:
import random,pygame
def Zimu():
PANEL_width = 1920
PANEL_highly = 1080
FONT_PX = 20
pygame.init()
# 创建一个窗口
winSur = pygame.display.set_mode((PANEL_width, PANEL_highly))
font = pygame.font.SysFont('123.ttf', 15)
bg_suface = pygame.Surface((PANEL_width, PANEL_highly), flags=pygame.SRCALPHA)
pygame.Surface.convert(bg_suface)
bg_suface.fill(pygame.Color(0, 0, 0, 28))
winSur.fill((0, 0, 0))
letter = ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'z', 'x', 'c',
'v', 'b', 'n', 'm','1','2','3','4','5','6','7','8','9','0','~','!','@','#','¥',
'%','&','*','!','@','#','$','%','^','&','*','(',')','-','=',']',
'{','}','\\','|','?','>','.',',','\'','\"',';',':','Á','É','Í', 'Ó', 'Ú', 'Ý', 'À', 'È', 'Ì' , 'Ò',
'Ù', 'Â' , 'Ê' , 'Î' , 'Ô' , 'Û' , 'Ä' , 'Ë' , 'Ï' , 'Ö' , 'Ü' , 'Ÿ',
'á' , 'é' , 'í' , 'ó' , 'ú' , 'ý' ,'à' , 'è' , 'ì' , 'ò' , 'ù' , 'â' , 'ê' , 'î' ,
'ô' , 'û' , 'ä' , 'ë' , 'ï' , 'ö' , 'ü' , 'ÿ',
'Ç', 'Ş' , 'Ã', 'Õ' , 'Ñ' , 'Ą' , 'Ę' ,'Į' , 'Ų' , 'Æ' , 'Œ' , 'Ø' , 'IJ' , 'Þ',
'ç' , 'ş' , 'ã', 'õ' , 'ñ', 'ą' , 'ę' , 'į' , 'ų', 'æ' , 'œ' , 'ø' , 'ij' , 'ß' , 'þ',
'α' , 'β' , 'Γ' , 'γ' , 'Δ' , 'δ' , 'Ε' ,'ε' , 'Ζ' , 'ζ' , 'Η' , 'η' , 'Θ' , 'θ' ,
'Ι', 'ι', 'κ' ,
'∧', 'λ', 'Μ' , 'μ', 'Ξ', 'ξ' ,
'∏' , 'π' , 'Ρ' , 'ρ' , '∑' , 'σ' , 'τ' , 'υ' ,'Φ' ,'φ' , 'χ' , 'Ψ' , 'ψ' , 'Ω', 'ω' ]
texts = [font.render(str(letter[i]), True, (0, 255, 0)) for i in range(len(letter))]
# 按窗口的宽度来计算可以在画板上放几列坐标并生成一个列表
column = int(PANEL_width / FONT_PX)
drops = [0 for i in range(column)]
while True:
# 从队列中获取事件
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
chang = pygame.key.get_pressed()
if (chang[27]):
exit()
# 暂停给定的毫秒数
pygame.time.delay(30)
# 重新编辑图像
winSur.blit(bg_suface, (0, 0))
for i in range(len(drops)):
text = random.choice(texts)
# 重新编辑每个坐标点的图像
winSur.blit(text, (i * FONT_PX, drops[i] * FONT_PX))
drops[i] += 1
if drops[i] * 10 > PANEL_highly or random.random() > 0.95:
drops[i] = 0
pygame.display.flip()
你可以看到我只有一个函数,是的,因为我们是用C++ 调用py脚本。
至于C++ 源代码,我没有给过多注释,对函数有问题的欢迎在评论区留言。
那么,我大致讲一下,这个调用的机理:
_getcwd(cwd, 256);//获得工程启动路径后,保存在cmd 字符数组中。
Py_Initialize();
和
Py_Finalize();
是很重要的,因为要初始化python C API与释放C API 资源。
那么中间就是调用了:
PyRun_SimpleString("import sys");
string sss = "sys.path.append('";
string ttt = "')";
string final = sss + cwd + ttt;
//PyRun_SimpleString("sys.path.append('D:/Python/CPP_INVOKE_PYTHON_2/CPP_INVOKE_PYTHON_2')");
replace(final.begin(), final.end(), '\\', '/');//将\\替换为/
const char* final_1 = final.c_str();
PyRun_SimpleString(final_1);
这一段代码是把你的工程目录加入到python解释器的环境变量中。
你之所以在黑框框里任何地方调用python 马上就能进入python 环境靠的就是你在安装python时,
把添加环境变量那个复选框打勾了。
那么C++ 调用python也是模仿一个python解释器执行python语句:
PyRun_SimpleString(final_1);
那么好了,如何调用,C++ 这里采用指针,也可以理解为脚本地址,是一个指向脚本在哪里的一个句柄。
pModule 就是指向你的py脚本。
// 3、调用python文件名,不用写后缀
PyObject* pModule = PyImport_ImportModule("matrix");
if (pModule == NULL) {
cout << "module not found" << endl;
return 1;
}
那么好了,如何调用里面的函数,C++ 这里采用函数指针,也可以理解为函数地址,是一个指向函数在哪里的一个句柄。
pFunc 就是这个句柄。
// 4、调用函数
PyObject* pFunc = PyObject_GetAttrString(pModule, "Zimu");
if (!pFunc || !PyCallable_Check(pFunc)) {
cout << "not found function add_num" << endl;
return 0;
}
好了,正式执行了:
PyObject_CallObject(pFunc, NULL);
我的函数是Zimu()
不涉及任何参数,所以是NULL。涉及到函数调用的请移步官方文档。
这是调用效果:
好了,自己试一试吧,这是一个比较成熟的技术,让python 的扩展性变得如此之大。
有个缺点移植性比较差,如果没有本机没有安装python并且没有安装必要的第三方库,或者python 的版本不对,就不可运行。
有心的朋友可以介绍下更多这方面的技术。