本文简单的讲解下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 的版本不对,就不可运行。
有心的朋友可以介绍下更多这方面的技术。

物联沃分享整理
物联沃-IOTWORD物联网 » C 调用 Python

发表评论