python与C C++的交互(一)

一.概述

Python是目前“大火“的编程语言,一种作为“胶水”的脚本语言,能得到如此的应用,确实有它独到之处。C/C++是一种“性能”编程语言,较为古老且经典,因与unix,linux,windows这些底层操作系统相关,而得到广泛应用。

Python从一开始就支持C语言的“嵌入”,这是python为弥补性能不足的一种措施,其实python的库就是用C编写的。

两种不同编程语言的应用之间的数据传递,可分为进程之间通讯和进程之内通讯。对于进程之间通讯,可采用thrift,protobuf以及自己定义的socket等方式进行,这不是本文的内容,本文关注的是后一种情况,即“嵌入式”交互,针对两种特定的编程语言,它们之间的交互也是特定的。

Python与C/C++交互包括两方面内容:

1.      python是主程序,从python中调用C/C++编写的内容;

2.      C/C++是主程序,从C/C++中调用python编写的内容;

当然,也存在嵌套的情况,例如,C/C++中调用python,python中反过来还需要再调用C/C++…

本文先说明交互的两个基础:数据类型的封装和python的生命周期管理,然后再分两部分说明上面1,2两种应用场景。


二. 环境搭建

本文以linux ubuntu 17.10环境为例,windows类似。

gcc和python3.6+的环境安装就不提了,需要注意的是,ubuntu下python的缺省安装不包含与c接口的开发库包,需另外安装,即需要安装python-dev。

安装完毕后,可查看一下是否有python.h和libpythonX.X.so,在编译C代码时,需要这两个文件,如果有,就OK了。

(以下C代码中都需要包含python.h,编译链接时需要libpythonX.X.so)


三.Python和C/C++环境中数据的传递封装

先不管采用什么方法,两种语言对各种数据类型的“表达”是不同的,例如整数,C语言中有int8,int16,int32,int64等,python中的变量因无类型说明,我们无从知道python中的一个整数究竟对应上述C中的哪个整数。

应该说,C语言中的数据类型更“原始”,更接近机器的表示形式,也就显得“标准”一些,python必须向C靠拢,因此,为方便两种语言数据类型的转换,python定义了若干转换函数。

C/C++环境中,

一类用于将python传递过来的数据转换为C/C++类型的:

PyArg_ParseTuple、PyArg_VaParse、PyArg_ParseTupleAndKeywords

一类用于将C/C++类型的数据回传给python的:

PyLong_FromLong、PyLong_FromDouble、Py_BuildValue

从C的角度,所有python类型都是PyObject,这有点像VC中的variant类型,python库对这些类型进行统一形式的封装和解封。

python环境中,

有一个专门的库ctypes负责将python类型转化为C类型:

c_long、c_doule

 总之,为保证二者的数据能够顺利转换,这些函数很多,详细的解释以官方文档为准。


四.Python中对象的引用计数

C语言中,变量的内存分配与释放是手工控制的,而python则采用了“reference counting(引用计数)”这样的技术来管理对象内存分配,因此我们在python中不必关心数据对象的内存管理问题,但C与python结合使用时,C中的代码使用python对象时,就必须符合python的对象引用规则了。

Python代码中的引用计数我们不要理会,C语言中的管理引用计数的函数为:

Py_DECREF/ Py_XDECREF

Py_INCREF/ Py_XINCREF

Python的引用计数是一个比较复杂的话题,官方文档也花了几个大的段落来说明,概要如下(主要点,并非完全):

1.      不存在拥有对象,而是拥有对象的引用(Nobody “owns” an object; however, you can own a reference to anobject)

2.      对于拥有的对象引用,有三种方式来处理:传递给别人、存储使用、释放(There are three ways to dispose of an owned reference: pass it on,store it, or call Py_DECREF())

3.      借用引用(borrowed reference)有好处,但要小心。

4.      引用管理规则

  • 大部分返回对象引用的函数也返回了对象引用的所有权,例如PyLong_FromLong()、Py_BuildValue(),将引用所有权传递给调用者,即使这个对象不是这个函数新创建的。
  • 从别的对象中“提取”出对象的许多函数,例如PyObject_GetAttrString(),也会将引用所有权传递给调用者。但是也有例外,如PyTuple_GetItem(), PyList_GetItem(),PyDict_GetItem(),PyDict_GetItemString(),它们返回的是“借用”的引用(即调用者不用释放返回的对象)。
  • PyImport_AddModule()返回的也是“借用”的引用。
  • 当向另一个函数传递对象时,通常,这个函数“借用”这个对象,如果这个函数需要存储这个对象,可以调用Py_INCREF()来独立“拥有”这个对象。但PyTuple_SetItem() 和 PyList_SetItem()是例外,它们“拥有”传递给它们的对象。PyDict_SetItem()等其他函数不“拥有”对象。
  • 当从python调用C写的函数,函数参数的引用是“借用”的,调用者“拥有”参数对象,因此,如果C函数想后续使用这些参数,必须调用Py_INCREF()来独立“拥有”它们。
  • 从C函数返回给python的对象,必须是C函数独立“拥有”的对象,此时,也将拥有权返回给python的调用者。

Python与C交互时对象引用的处理,有时是比较微妙的,官方文档也给出了例子,在我们没有能力理解它们背后的处理流程时,只能小心再小心了,否则内存泄漏或崩溃是必然的。


展开阅读全文

没有更多推荐了,返回首页