如何实现 C/C++ 与 Python 的通信

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何实现 C/C++ 与 Python 的通信相关的知识,希望对你有一定的参考价值。

用C/C++对脚本语言的功能扩展是非常常见的事情,Python也不例外。除了SWIG,市面上还有若干用于Python扩展的工具包,比较知名的还有Boost.Python、SIP等,此外,Cython由于可以直接集成C/C++代码,并方便的生成Python模块,故也可以完成扩展Python的任务。

答主在这里选用SWIG的一个重要原因是,它不仅可以用于Python,也可以用于其他语言。如今SWIG已经支持C/C++的好基友Java,主流脚本语言Python、Perl、Ruby、phpjavascript、tcl、Lua,还有Go、C#,以及R。SWIG是基于配置的,也就是说,原则上一套配置改变不同的编译方法就能适用各种语言(当然,这是理想情况了……)

SWIG的安装方便,有Windows的预编译包,解压即用,绿色健康。主流Linux通常集成swig的包,也可以下载源代码自己编译,SWIG非常小巧,通常安装不会出什么问题。

用SWIG扩展Python,你需要有一个待扩展的C/C++库。这个库有可能是你自己写的,也有可能是某个项目提供的。这里举一个不浮夸的例子:希望在Python中用到SSE4指令集的CRC32指令。

首先打开指令集的文档
可以看到有6个函数。分析6个函数的原型,其参数和返回值都是简单的整数。于是书写SWIG的配置文件(为了简化起见,未包含2个64位函数):

/* File: mymodule.i */
%module mymodule

%
#include "nmmintrin.h"
%

int _mm_popcnt_u32(unsigned int v);
unsigned int _mm_crc32_u8 (unsigned int crc, unsigned char v);
unsigned int _mm_crc32_u16(unsigned int crc, unsigned short v);
unsigned int _mm_crc32_u32(unsigned int crc, unsigned int v);

接下来使用SWIG将这个配置文件编译为所谓Python Module Wrapper

swig -python mymodule.i

得到一个 mymodule_wrap.c和一个mymodule.py。把它编译为Python扩展:

Windows:
cl /LD mymodule_wrap.c /o _mymodule.pyd -IC:\Python27\include C:\Python27\libs\python27.lib

Linux:
gcc -fPIC -shared mymodule_wrap.c -o _mymodule.so -I/usr/include/python2.7/ -lpython2.7

注意输出文件名前面要加一个下划线。
现在可以立即在Python下使用这个module了:

>>> import mymodule
>>> mymodule._mm_popcnt_u32(10)
2

回顾这个配置文件分为3个部分:

定义module名称mymodule,通常,module名称要和文件名保持一致。
% % 包裹的部分是C语言的代码,这段代码会原封不动的复制到mymodule_wrap.c
欲导出的函数签名列表。直接从头文件里复制过来即可。

还记得本文第2节的那个great_function吗?有了SWIG,事情就会变得如此简单:
/* great_module.i */
%module great_module
%
int great_function(int a)
return a + 1;

%
int great_function(int a);

换句话说,SWIG自动完成了诸如Python类型转换、module初始化、导出代码表生成的诸多工作。

对于C++,SWIG也可以应对。例如以下代码有C++类的定义:
//great_class.h
#ifndef GREAT_CLASS
#define GREAT_CLASS
class Great
private:
int s;
public:
void setWall (int _s) s = _s;;
int getWall () return s;;
;
#endif // GREAT_CLASS

对应的SWIG配置文件
/* great_class.i */
%module great_class
%
#include "great_class.h"
%
%include "great_class.h"

这里不再重新敲一遍class的定义了,直接使用SWIG的%include指令

SWIG编译时要加-c++这个选项,生成的扩展名为cxx
swig -c++ -python great_class.i

Windows下编译:
cl /LD great_class_wrap.cxx /o _great_class.pyd -IC:\Python27\include C:\Python27\libs\python27.lib

Linux,使用C++的编译器
g++ -fPIC -shared great_class_wrap.cxx -o _great_class.so -I/usr/include/python2.7/ -lpython2.7

在Python交互模式下测试:
>>> import great_class
>>> c = great_class.Great()
>>> c.setWall(5)
>>> c.getWall()
5

也就是说C++的class会直接映射到Python class

SWIG非常强大,对于Python接口而言,简单类型,甚至指针,都无需人工干涉即可自动转换,而复杂类型,尤其是自定义类型,SWIG提供了typemap供转换。而一旦使用了typemap,配置文件将不再在各个语言当中通用。

参考资料:
SWIG的官方文档,质量比较高。SWIG Users Manual
有个对应的中文版官网,很多年没有更新了。

写在最后:
由于CPython自身的结构设计合理,使得Python的C/C++扩展非常容易。如果打算快速完成任务,Cython(C/C++调用Python)和SWIG(Python调用C/C++)是很不错的选择。但是,一旦涉及到比较复杂的转换任务,无论是继续使用Cython还是SWIG,仍然需要学习Python源代码。

本文使用的开发环境:
Python 2.7.10
Cython 0.22
SWIG 3.0.6
Windows 10 x64 RTM
CentOS 7.1 AMD 64
Mac OSX 10.10.4
参考技术A socket通信?

如何使用 Linux 的虚拟串口使 C 程序与 Python 程序通信?

【中文标题】如何使用 Linux 的虚拟串口使 C 程序与 Python 程序通信?【英文标题】:How to make C program communicate with Python program using Linux's virtual serial ports? 【发布时间】:2019-03-23 17:47:42 【问题描述】:

我想将数据从 C 程序发送到 Python 程序,该程序将可视化这些数据。开发环境是一台Linux(Ubuntu 18.04LTS)计算机。更清楚地说,这两个程序都在同一台计算机上运行。

我在 C 程序中使用 termios 打开串口,在 Python 端使用 pySerial。至于串口,我使用的是“ttyS0”。问题是,当我从 C 程序向 Python 程序发送“Hello”并在终端上打印时,我看到的是空格字符,基本上我得到了这个“”。

我的问题是,我可以为此目的使用“ttyS0”串行端口(我猜那是一个虚拟端口)吗?

这是 C 代码:

#include <stdint.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>
#include <time.h>

// Termios init functions are not posted because the configuration
// is correct and proved that they are working.

int main()

    char *portname = "/dev/ttyS0";
    int fd;
    int wlen;
    unsigned char writeBuffer[] = "Hello!";

    fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
    if (fd < 0) 
        printf("Error opening %s: %s\n", portname, strerror(errno));
        return -1;
    
    /*baudrate 115200, 8 bits, no parity, 1 stop bit */
    set_interface_attribs(fd, B115200);

    do
        wlen = write(fd, writeBuffer, sizeof(writeBuffer));
        printf("Sent data is: \"%s\"\n", writeBuffer);
        delay(500);
     while(1);

Python 代码:

import serial
from time import sleep

port = "/dev/ttyS0"
ser = serial.Serial(port, 115200, timeout=0.5)

while True:
    data = ser.readline()   
    print(str(data.decode('utf-8')))
ser.close()

【问题讨论】:

【参考方案1】:

ttyS0 是您计算机的串行端口——它没有什么“虚拟”的。写入此设备将尝试使用该端口将数据传输出计算机,从该设备读取将尝试从连接到该端口的外部设备接收数据。同一台计算机上的两个程序无法使用串行端口进行有效通信。

我认为您在这里寻找的是pipe、socket pair 或pty。哪一个最合适取决于您的具体要求。

【讨论】:

以上是关于如何实现 C/C++ 与 Python 的通信的主要内容,如果未能解决你的问题,请参考以下文章

如何实现 C/C++ 与 Python 的通信

C/S结构的程序如何实现客户端与服务端的通信

LINUX下,如何使用C/C++对EXCEL进行读写!

用C/C++实现字符串的创建,并进行查找与替换

如何使用 Linux 的虚拟串口使 C 程序与 Python 程序通信?

c/c++/c++11 浅拷贝和深拷贝