Python与C/C++的相互调用案例


Posted in Python onMarch 04, 2021

一、问题

Python模块和C/C++的动态库间相互调用在实际的应用中会有所涉及,在此作一总结。

二、Python调用C/C++

1、Python调用C动态链接库

Python调用C库比较简单,不经过任何封装打包成so,再使用python的ctypes调用即可。

(1)C语言文件:pycall.c

/***gcc -o libpycall.so -shared -fPIC pycall.c*/
#include <stdio.h>
#include <stdlib.h>
int foo(int a, int b)
{
 printf("you input %d and %d\n", a, b);
 return a+b;
}

(2)gcc编译生成动态库libpycall.so:gcc -o libpycall.so -shared -fPIC pycall.c。使用g++编译生成C动态库的代码中的函数或者方法时,需要使用extern "C"来进行编译。

(3)Python调用动态库的文件:pycall.py

import ctypes
ll = ctypes.cdll.LoadLibrary 
lib = ll("./libpycall.so") 
lib.foo(1, 3)
print '***finish***'

(4)运行结果:

Python与C/C++的相互调用案例

2、Python调用C++(类)动态链接库

需要extern "C"来辅助,也就是说还是只能调用C函数,不能直接调用方法,但是能解析C++方法。不是用extern "C",构建后的动态链接库没有这些函数的符号表。

(1)C++类文件:pycallclass.cpp

#include <iostream>
using namespace std;
 
class TestLib
{
 public:
  void display();
  void display(int a);
};
void TestLib::display() {
 cout<<"First display"<<endl;
}
void TestLib::display(int a) {
 cout<<"Second display:"<<a<<endl;
}
extern "C" {
 TestLib obj;
 void display() {
  obj.display(); 
  }
 void display_int() {
  obj.display(2); 
  }
}

(2)g++编译生成动态库libpycall.so:g++ -o libpycallclass.so -shared -fPIC pycallclass.cpp。

(3)Python调用动态库的文件:pycallclass.py

import ctypes
so = ctypes.cdll.LoadLibrary 
lib = so("./libpycallclass.so") 
print 'display()'
lib.display()
print 'display(100)'
lib.display_int(100)

(4)运行结果:

Python与C/C++的相互调用案例

3、Python调用C/C++可执行程序

(1)C/C++程序:main.cpp

#include <iostream>
using namespace std;
int test()
{
 int a = 10, b = 5;
 return a+b;
}
int main()
{
 cout<<"---begin---"<<endl;
 int num = test();
 cout<<"num="<<num<<endl;
 cout<<"---end---"<<endl;
}

(2)编译成二进制可执行文件:g++ -o testmain main.cpp。

(3) Python调用程序:main.py

import commands
import os
main = "./testmain"
if os.path.exists(main):
 rc, out = commands.getstatusoutput(main)
 print 'rc = %d, \nout = %s' % (rc, out)
 
print '*'*10
f = os.popen(main) 
data = f.readlines() 
f.close() 
print data
 
print '*'*10
os.system(main)

(4)运行结果:

Python与C/C++的相互调用案例

4、扩展Python(C++为Python编写扩展模块)

所有能被整合或导入到其它python脚本的代码,都可以被称为扩展。可以用Python来写扩展,也可以用C和C++之类的编译型的语言来写扩展。Python在设计之初就考虑到要让模块的导入机制足够抽象。抽象到让使用模块的代码无法了解到模块的具体实现细节。Python的可扩展性具有的优点:方便为语言增加新功能、具有可定制性、代码可以实现复用等。

为 Python 创建扩展需要三个主要的步骤:创建应用程序代码、利用样板来包装代码和编译与测试。

(1)创建应用程序代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int fac(int n)
{
 if (n < 2) return(1); /* 0! == 1! == 1 */
 return (n)*fac(n-1); /* n! == n*(n-1)! */
}
char *reverse(char *s)
{
 register char t,     /* tmp */
   *p = s,      /* fwd */
   *q = (s + (strlen(s) - 1)); /* bwd */
 while (p < q)    /* if p < q */
 {
  t = *p;   /* swap & move ptrs */
  *p++ = *q;
  *q-- = t;
 }
 return(s);
}
int main()
{
 char s[BUFSIZ];
 printf("4! == %d\n", fac(4));
 printf("8! == %d\n", fac(8));
 printf("12! == %d\n", fac(12));
 strcpy(s, "abcdef");
 printf("reversing 'abcdef', we get '%s'\n", \
  reverse(s));
 strcpy(s, "madam");
 printf("reversing 'madam', we get '%s'\n", \
  reverse(s));
 return 0;
}

上述代码中有两个函数,一个是递归求阶乘的函数fac();另一个reverse()函数实现了一个简单的字符串反转算法,其主要目的是修改传入的字符串,使其内容完全反转,但不需要申请内存后反着复制的方法。

(2)用样板来包装代码

接口的代码被称为“样板”代码,它是 应用程序代码与Python解释器之间进行交互所必不可少的一部分。样板主要分为4步:a、包含Python的头文件;b、为每个模块的每一个函数增加一个型如PyObject* Module_func()的包装函数;c、为每个模块增加一个型如PyMethodDef ModuleMethods[]的数组;d、增加模块初始化函数void initModule()。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int fac(int n)
{
 if (n < 2) return(1);
 return (n)*fac(n-1);
}
char *reverse(char *s)
{
 register char t,
   *p = s,
   *q = (s + (strlen(s) - 1));
 while (s && (p < q))
 {
  t = *p;
  *p++ = *q;
  *q-- = t;
 }
 return(s);
}
int test()
{
 char s[BUFSIZ];
 printf("4! == %d\n", fac(4));
 printf("8! == %d\n", fac(8));
 printf("12! == %d\n", fac(12));
 strcpy(s, "abcdef");
 printf("reversing 'abcdef', we get '%s'\n", \
  reverse(s));
 strcpy(s, "madam");
 printf("reversing 'madam', we get '%s'\n", \
  reverse(s));
 return 0;
}
#include "Python.h"
static PyObject *
Extest_fac(PyObject *self, PyObject *args)
{
 int num;
 if (!PyArg_ParseTuple(args, "i", &num))
  return NULL;
 return (PyObject*)Py_BuildValue("i", fac(num));
}
static PyObject *
Extest_doppel(PyObject *self, PyObject *args)
{
 char *orig_str;
 char *dupe_str;
 PyObject* retval;
 if (!PyArg_ParseTuple(args, "s", &orig_str))
  return NULL;
 retval = (PyObject*)Py_BuildValue("ss", orig_str,
  dupe_str=reverse(strdup(orig_str)));
 free(dupe_str);    #防止内存泄漏
 return retval;
}
static PyObject *
Extest_test(PyObject *self, PyObject *args)
{
 test();
 return (PyObject*)Py_BuildValue("");
}
static PyMethodDef
ExtestMethods[] =
{
 { "fac", Extest_fac, METH_VARARGS },
 { "doppel", Extest_doppel, METH_VARARGS },
 { "test", Extest_test, METH_VARARGS },
 { NULL, NULL },
};
void initExtest()
{
 Py_InitModule("Extest", ExtestMethods);
}

Python.h头文件在大多数类Unix系统中会在/usr/local/include/python2.x或/usr/include/python2.x目录中,系统一般都会知道文件安装的路径。

增加包装函数,所在模块名为Extest,那么创建一个包装函数叫Extest_fac(),在Python脚本中使用是先import Extest,然后调用Extest.fac(),当 Extest.fac()被调用时,包装函数 Extest_fac()会被调用,包装函数接受一个 Python的整数参数,把它转为C的整数,然后调用C的fac()函数,得到一个整型的返回值,最后把这个返回值转为Python的整型数做为整个函数调用的结果返回回去。其他两个包装函数Extest_doppel()和Extest_test()类似。

从Python到C的转换用PyArg_Parse*系列函数, int PyArg_ParseTuple():把Python传过来的参数转为C;int PyArg_ParseTupleAndKeywords()与PyArg_ParseTuple()作用相同,但是同时解析关键字参数;它们 的用法跟C的sscanf函数很像,都接受一个字符串流,并根据一个指定的格式字符串进行解析,把结果放入到相应的指针所指的变量中去,它们的返回值为1表示解析成功,返回值为0表示失败。 从C到Python的转换函数是PyObject* Py_BuildValue():把C的数据转为Python的一个对象或一组对象,然后返回之;Py_BuildValue的用法跟sprintf很像,把所有的参数按格式字符串所指定的格式转换成一个Python的对象。

C与Python之间数据转换的转换代码:

Python与C/C++的相互调用案例

为每个模块增加一个型如PyMethodDef ModuleMethods[]的数组,以便于Python解释器能够导入并调用它们,每一个数组都包含了函数在Python中的名字,相应的包装函数的名字以及一个METH_VARARGS常量,METH_VARARGS表示参数以tuple形式传入。 若需要使用 PyArg_ParseTupleAndKeywords()函数来分析命名参数的话,还需要让这个标志常量与METH_KEYWORDS常量进行逻辑与运算常量 。数组最后用两个NULL来表示函数信息列表的结束。

所有工作的最后一部分就是模块的初始化函数,调用Py_InitModule()函数,并把模块名和ModuleMethods[]数组的名字传递进去,以便于解释器能正确的调用模块中的函数。

(3)编译

为了让新Python的扩展能被创建,需要把它们与Python库放在一起编译,distutils包被用来编译、安装和分发这些模块、扩展和包。

创建一个setup.py 文件,编译最主要的工作由setup()函数来完成:

#!/usr/bin/env python 
from distutils.core import setup, Extension 
MOD = 'Extest'
setup(name=MOD, ext_modules=[Extension(MOD, sources=['Extest2.c'])])

Extension()第一个参数是(完整的)扩展的名字,如果模块是包的一部分的话,还要加上用'.'分隔的完整的包的名字。上述的扩展是独立的,所以名字只要写"Extest"就行;sources参数是所有源代码的文件列表,只有一个文件Extest2.c。setup需要两个参数:一个名字参数表示要编译哪个内容;另一个列表参数列出要编译的对象,上述要编译的是一个扩展,故把ext_modules参数的值设为扩展模块的列表。

运行setup.py build命令就可以开始编译我们的扩展了,提示部分信息:

creating build/lib.linux-x86_64-2.6
gcc -pthread -shared build/temp.linux-x86_64-2.6/Extest2.o -L/usr/lib64 -lpython2.6 -o build/lib.linux-x86_64-2.6/Extest.so

(4)导入和测试

你的扩展会被创建在运行setup.py脚本所在目录下的build/lib.*目录中,可以切换到那个目录中来测试模块,或者也可以用命令把它安装到Python中:python setup.py install,会提示相应信息。

测试模块:

Python与C/C++的相互调用案例

(5)引用计数和线程安全

Python对象引用计数的宏:Py_INCREF(obj)增加对象obj的引用计数,Py_DECREF(obj)减少对象obj的引用计数。Py_INCREF()和Py_DECREF()两个函数也有一个先检查对象是否为空的版本,分别为Py_XINCREF()和Py_XDECREF()。

编译扩展的程序员必须要注意,代码有可能会被运行在一个多线程的Python环境中。这些线程使用了两个C宏Py_BEGIN_ALLOW_THREADS和Py_END_ALLOW_THREADS, 通过将代码和线程隔离,保证了运行和非运行时的安全性,由这些宏包裹的代码将会允许其他线程的运行。

三、C/C++调用Python

C++可以调用Python脚本,那么就可以写一些Python的脚本接口供C++调用了,至少可以把Python当成文本形式的动态链接库,

需要的时候还可以改一改,只要不改变接口。缺点是C++的程序一旦编译好了,再改就没那么方便了。

(1)Python脚本:pytest.py

#test function
def add(a,b):
 print "in python function add"
 print "a = " + str(a)
 print "b = " + str(b)
 print "ret = " + str(a+b)
 return
 
def foo(a):
 
 print "in python function foo"
 print "a = " + str(a)
 print "ret = " + str(a * a)
 return 
 
class guestlist:
 def __init__(self):
  print "aaaa"
 def p():
 print "bbbbb"
 def __getitem__(self, id):
 return "ccccc"
def update():
 guest = guestlist()
 print guest['aa']
 
#update()

(2)C++代码:

/**g++ -o callpy callpy.cpp -I/usr/include/python2.6 -L/usr/lib64/python2.6/config -lpython2.6**/
#include <Python.h>
int main(int argc, char** argv)
{
 // 初始化Python
 //在使用Python系统前,必须使用Py_Initialize对其
 //进行初始化。它会载入Python的内建模块并添加系统路
 //径到模块搜索路径中。这个函数没有返回值,检查系统
 //是否初始化成功需要使用Py_IsInitialized。
 Py_Initialize();
 
 // 检查初始化是否成功
 if ( !Py_IsInitialized() ) {
  return -1;
 }
 // 添加当前路径
 //把输入的字符串作为Python代码直接运行,返回0
 //表示成功,-1表示有错。大多时候错误都是因为字符串
 //中有语法错误。
 PyRun_SimpleString("import sys");
 PyRun_SimpleString("print '---import sys---'"); 
 PyRun_SimpleString("sys.path.append('./')");
 PyObject *pName,*pModule,*pDict,*pFunc,*pArgs;
 
 // 载入名为pytest的脚本
 pName = PyString_FromString("pytest");
 pModule = PyImport_Import(pName);
 if ( !pModule ) {
  printf("can't find pytest.py");
  getchar();
  return -1;
 }
 pDict = PyModule_GetDict(pModule);
 if ( !pDict ) {
  return -1;
 }
 
 // 找出函数名为add的函数
 printf("----------------------\n");
 pFunc = PyDict_GetItemString(pDict, "add");
 if ( !pFunc || !PyCallable_Check(pFunc) ) {
  printf("can't find function [add]");
  getchar();
  return -1;
  }
 
 // 参数进栈
 PyObject *pArgs;
 pArgs = PyTuple_New(2);
 
 // PyObject* Py_BuildValue(char *format, ...)
 // 把C++的变量转换成一个Python对象。当需要从
 // C++传递变量到Python时,就会使用这个函数。此函数
 // 有点类似C的printf,但格式不同。常用的格式有
 // s 表示字符串,
 // i 表示整型变量,
 // f 表示浮点数,
 // O 表示一个Python对象。
 
 PyTuple_SetItem(pArgs, 0, Py_BuildValue("l",3));
 PyTuple_SetItem(pArgs, 1, Py_BuildValue("l",4));
 
 // 调用Python函数
 PyObject_CallObject(pFunc, pArgs);
 
 //下面这段是查找函数foo 并执行foo
 printf("----------------------\n");
 pFunc = PyDict_GetItemString(pDict, "foo");
 if ( !pFunc || !PyCallable_Check(pFunc) ) {
  printf("can't find function [foo]");
  getchar();
  return -1;
  }
 
 pArgs = PyTuple_New(1);
 PyTuple_SetItem(pArgs, 0, Py_BuildValue("l",2)); 
 
 PyObject_CallObject(pFunc, pArgs);
  
 printf("----------------------\n");
 pFunc = PyDict_GetItemString(pDict, "update");
 if ( !pFunc || !PyCallable_Check(pFunc) ) {
  printf("can't find function [update]");
  getchar();
  return -1;
  }
 pArgs = PyTuple_New(0);
 PyTuple_SetItem(pArgs, 0, Py_BuildValue(""));
 PyObject_CallObject(pFunc, pArgs);  
 
 Py_DECREF(pName);
 Py_DECREF(pArgs);
 Py_DECREF(pModule);
 
 // 关闭Python
 Py_Finalize();
 return 0;
}

(3)C++编译成二进制可执行文件:g++ -o callpy callpy.cpp -I/usr/include/python2.6 -L/usr/lib64/python2.6/config -lpython2.6,编译选项需要手动指定Python的include路径和链接接路径(Python版本号根据具体情况而定)。

(4)运行结果:

Python与C/C++的相互调用案例

四、总结

(1)Python和C/C++的相互调用仅是测试代码,具体的项目开发还得参考Python的API文档。

(2)两者交互,C++可为Python编写扩展模块,Python也可为C++提供脚本接口,更加方便于实际应用。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

Python 相关文章推荐
Python简单检测文本类型的2种方法【基于文件头及cchardet库】
Sep 18 Python
有趣的python小程序分享
Dec 05 Python
Python3.4 splinter(模拟填写表单)使用方法
Oct 13 Python
pandas 条件搜索返回列表的方法
Oct 30 Python
Opencv实现抠图背景图替换功能
May 21 Python
Django框架之登录后自定义跳转页面的实现方法
Jul 18 Python
Flask框架学习笔记之路由和反向路由详解【图文与实例】
Aug 12 Python
Python 分发包中添加额外文件的方法
Aug 16 Python
查看jupyter notebook每个单元格运行时间实例
Apr 22 Python
Python抖音快手代码舞(字符舞)的实现方法
Feb 07 Python
python使用pymysql模块操作MySQL
Jun 16 Python
Python游戏开发实例之graphics实现AI五子棋
Nov 01 Python
解决Python import .pyd 可能遇到路径的问题
Mar 04 #Python
关于PySnooper 永远不要使用print进行调试的问题
Mar 04 #Python
pip/anaconda修改镜像源,加快python模块安装速度的操作
Mar 04 #Python
Pytorch实现WGAN用于动漫头像生成
Mar 04 #Python
基于PyInstaller各参数的含义说明
Mar 04 #Python
解决Pyinstaller打包软件失败的一个坑
Mar 04 #Python
selenium+python自动化78-autoit参数化与批量上传功能的实现
Mar 04 #Python
You might like
短波的认识
2021/03/01 无线电
构建简单的Webmail系统
2006/10/09 PHP
php显示指定目录下子目录的方法
2015/03/20 PHP
给WordPress中的留言加上楼层号的PHP代码实例
2015/12/14 PHP
用javascript将数据库中的TEXT类型数据动态赋值到TEXTAREA中
2007/04/20 Javascript
(推荐一个超好的JS函数库)S.Sams Lifexperience ScriptClassLib
2007/04/29 Javascript
jQuery 判断页面元素是否存在的代码
2009/08/14 Javascript
js string 转 int 注意的问题小结
2013/08/15 Javascript
Node.js中对通用模块的封装方法
2014/06/06 Javascript
浅谈javascript的分号的使用
2015/05/12 Javascript
js实现黑色简易的滑动门网页tab选项卡效果
2015/08/31 Javascript
ES6中Math对象的部分扩展
2017/02/20 Javascript
深入理解基于vue-cli的vuex配置
2017/07/24 Javascript
vue-cli脚手架-bulid下的配置文件
2018/03/27 Javascript
jquery插件开发模式实例详解
2019/07/20 jQuery
Vue.js组件props数据验证实现详解
2019/10/19 Javascript
JS实现百度搜索框关键字推荐
2020/02/17 Javascript
Vue中点击active并第一个默认选中功能的实现
2020/02/24 Javascript
[13:38]2015国际邀请赛中国战队出征仪式
2015/05/29 DOTA
python类装饰器用法实例
2015/06/04 Python
使用Python导出Excel图表以及导出为图片的方法
2015/11/07 Python
python打包压缩、读取指定目录下的指定类型文件
2018/04/12 Python
Python 2.7中文显示与处理方法
2018/07/16 Python
Flask之flask-session的具体使用
2018/07/26 Python
浅谈Python_Openpyxl使用(最全总结)
2019/09/05 Python
python 实现屏幕录制示例
2019/12/23 Python
Django视图、传参和forms验证操作
2020/07/15 Python
CSS3中的opacity属性使用教程
2015/08/19 HTML / CSS
CSS的background属性及CSS3的背景图片设置总结
2016/06/13 HTML / CSS
详解CSS3 rem(设置字体大小) 教程
2017/11/21 HTML / CSS
班级聚会策划书
2014/01/16 职场文书
赞美老师的演讲稿
2014/05/22 职场文书
查摆问题自我剖析材料
2014/08/18 职场文书
科级干部群众路线教育实践活动对照检查材料思想汇报
2014/09/20 职场文书
2014幼儿园大班工作总结
2014/11/10 职场文书
Redisson实现Redis分布式锁的几种方式
2021/08/07 Redis