python调用c/c++概述

因为实验室项目的进度,需要把之前用c写的接口程序转为python。因为c的执行速度更高,所以考虑保持原来的c代码不变,用python直接调用其中的函数。实际中也经常用这样的方法来给python加速,参考博客[1]中就提到了,因为他们的程序中有特别大的双层循环,还有位操作,所以效果明显——加速后C运行速度比python快了1000倍。这就来到了如何在python中调用c/c++代码的问题。

在python和c/c++交互使用中,有以下几个不同方面:

python调用c/c++概述

本文的重点在介绍python调用c/c++动态链接库,特别是应用ctypes进行类型转换,包括自己在尝试过程中遇到的问题。其他部分简单介绍并给一些博客链接。

 

一、 python调用c/c++动态链接库

python调用动态链接库这件事本身浅显易懂。总体就三步:

1、 编写c语言文件

python调用c/c++概述

2、 gcc编译生成动态链接库

3、 python导入动态链接库并调用函数

导入ctypes,通过ctypes导入动态链接库。

python调用c/c++概述

这样就成功将c嵌入到python中了。

 

但是问题就来了。我们一般调用的函数不会这么简单的。通常我们都是希望在c中完成复杂的计算,再把结果返回给python。所以我们需要通过调用来传入参数(很可能是数组),同时返回结果(往往也是数组)。这就需要提一下ctypes到底是个什么东西了。

 

ctypes

ctypes is a foreign function library forPython. It provides C compatible data types, and allows calling functions inDLLs or shared libraries. It can be used to wrap these libraries in purePython.[3]

[3]中是官方的说明手册,解释的都很清楚,强烈推荐!

 

ctypes可以处理传参的参数类型转换:

python调用c/c++概述

中间那列是c中的参数类型,右边是python参数类型。除了int, long, string,unicode string之外都需要转换,两者之间的转换可以通过ctypes类型完成。[4]中详细解释了传指针的方法,可以参考。

ctypes可以调用c/c++文件中的函数,这里需要指明传入参数的类型和返回参数的类型:

python调用c/c++概述

这个函数是我昨天写的,用于调用读取机械臂状态信息的c文件。第30行设定了传入参数类型(float指针),第31行设定了返回参数的类型(int)。c函数的计算结果保存在传入参数的所在地址,所以提前给ret_value开辟空间。之后就是调用函数和返回处理了。通过这样的方法,可以成功调用c代码,通过TCP通信获得机械臂的实时位置。

除了之前那张表中所列的参数类型,还可以自己定义结构体。可以参考[4]。不知为何,笔者自己想通过返回值传递float数组指针的测试失败了,返回结构体的指针是可行的,如下图。(感觉似乎犯了什么非常蠢的错误)

python调用c/c++概述

 

至此,这个问题还没有完。剩下的问题都是编译的问题:

1、 OSError: undefined Symbol

出现了这个错误,是因为gcc/g++编译的时候没有包含依赖库和头文件。

可以用ldd -r xxx.so来查看是否是动态链接库本身的问题。

python调用c/c++概述

2、 c与cpp

对于.c文件,使用的是c编译器,编译指令可以为:

gcc -o lib.so -fPIC -shared xxx.c

但是如果使用c++编译器,例如g++,或者仅仅是把源代码命名为xxx.cpp,都会导致编译后的动态库无法调用。出现AttributeError:undefined symbol的错误,如下图。

python调用c/c++概述

此时的解决办法是,把源代码.cpp文件中所有函数前面都加上extern “C”,强迫使用c编译器进行编译。[5]

编译部分的东西不是很懂,我理解的大概是这样。希望研究生期间有机会能上编译、优化的课程。

 

二、 python调用c/c++可执行程序

将c/c++代码编译为可执行程序,之后在python中执行系统程序,可以通过argv传入参数,并读取程序运行的输出。

但这种方法有诸多不便之处。例如一个可执行程序只能执行有限的功能,所有参数都必须传入main函数,并通过print输出结果。如果想让c/c++代码作为一个多功呢个库,这就很难实现。

 

三、 扩展python

需要#include<Python.h>。

原理就是将c/c++代码当作python的模块,与用python代码编写的功能模块一样,在python中对这些模块进行调用而无需了解模块中的实现细节。

为了让python解释器能够理解模块中执行的内容,需要中间过渡,接口的代码称为样板代码。为Python创建扩展需要三个主要的步骤:创建应用程序代码、利用样板来包装代码和编译与测试。即先编写实现相应功能的c代码,用python接口来包装所有函数,最后在python中调用测试。具体见[2]。

 

四、 c/c++调用python

需要#include<Python.h>。

把python文件当作文本形式的动态链接库,只要不改变接口,需要的时候还可以修改python库。但是c文件编译好之后就不好改了。这里注意,编译的时候要手动指定python的include路径和链接路径。[2]

 

参考博客:

[1] 利用ctypes给python加速. https://blog.****.net/thesby/article/details/76283807.

[2]Python实例浅谈之三Python与c/c++相互调用. https://www.cnblogs.com/apexchu/p/5015961.html.

[3] PythonDocumentation: ctypes. https://docs.python.org/2/library/ctypes.html.

[4]python ctypes 探究 ---- python与c交互. http://www.cnblogs.com/night-ride-depart/p/4907613.html.

[5] linux平台上面python调用c. http://www.cnblogs.com/laodageblog/p/3757867.html.