Python 程序和 C 程序的整合

Python 是一种用于快速开发软件的编程语言,它的语法比较简单,易于掌握,但存在执行速度慢的问题,并且在处理某些问题时存在不足,如对计算机硬件系统的访问,对媒体文件的访问等。而作为软件开发的传统编程语言—— C 语言,却能在这些问题上很好地弥补 Python 语言的不足。因此,本文通过实例研究如何在 Python 程序中整合既有的 C 语言模块,包括用 C 语言编写的源程序和动态链接库等,从而充分发挥 Python 语言和 C 语言各自的优势。

利用 ctypes 模块整合 Python 程序和 C 程序(C/C++)

C 语言的特点

C 语言作为最受人们欢迎的语言之一,有广泛的发展基础。简洁紧凑、灵活方便,功能强大是其特点。另外,C 语言是一门中级语言。它把高级语言的基本结构和语句与低级语言的实用性结合起来。由于可以直接访问物理地址,可以方便的对硬件进行操作。因此,很多的系统软件都是由 C 语言编写。

Python 语言与 C 语言的交互

为了节省软件开发成本,软件开发人员希望能够缩短的软件的开发时间,希望能够在短时间内开发出稳定的产品。Python 功能强大,简单易用,能够快速开发应用软件。但是由于 Python 自身执行速度的局限性,对性能要求比较高的模块需要使用效率更高的程序语言进行开发,例如 C 语言,系统的其他模块运用 Python 进行快速开发,最后将 C 语言开发的模块与 Python 开发的模块进行整合。在此背景下,基于 Python 语言与 C 语言的各自特点,用 C 语言来扩展现有的 Python 程序,显得很有意义。本文首先介绍几种常用的整合 Python 程序与 C 语言程序的方法,最后给出相应的实例。

ctypes 模块

ctypes 是 Python 的一个标准模块,它包含在 Python2.3 及以上的版本里。ctypes 是一个 Python 的高级外部函数接口,它使得 Python 程序可以调用 C 语言编译的静态链接库和动态链接库。运用 ctypes 模块,能够在 Python 源程序中创建,访问和操作简单的或复杂的 C 语言数据类型。最为重要的是 ctypes 模块能够在多个平台上工作,包括 Windows,Windows CE,Mac OS X,Linux,Solaris,FreeBSD,OpenBSD。

源代码层面上的整合

利用 Python 本身提供的 ctypes 模块可以使 Python 语言和 C 语言在源代码层面上进行整合。
表 ctypes,c 语言和 Python 语言变量类型关系:

ctypes typec typePython type
c_charchar1-character string
c_wcharwchar_t1-character unicode string
c_bytecharint/long
c_ubyteunsigned charint/long
c_shortshortint/long
c_ushortunsigned shortint/long
c_intintint/long
c_uintunsigned intint/long
c_longlongint/long
c_ulongunsigned longint/long
c_longlong__int64 or long longint/long
c_ulonglongunsigned __int64 or unsigned long longint/long
c_floatfloatfloat
c_doubledoublefloat
c_char_pchar * (NUL terminated)string or None
c_wchar_pwchar_t * (NUL terminated)unicode or None
c_void_pvoid *int/long or None

表中的第一列是在 ctypes 库中定义的变量类型,第二列是 C 语言定义的变量类型,第三列是 Python 语言在不使用 ctypes 时定义的变量类型。

  1. ctypes简单使用:
    >> from ctypes import * # 导入 ctypes 库中所有模块
    >>i = c_int(45) # 定义一个 int 型变量,值为 45
    >>i.value # 打印变量的值
    45
    >>i.value = 56 # 改变该变量的值为 56
    >>i.value # 打印变量的新值
    56
  2. ctypes 使用 C 语言变量
    >> p = create_string_buffer(10) # 定义一个可变字符串变量,长度为 10
    >> p.raw # 初始值是全 0,即 C 语言中的字符串结束符’ \0 ’
    ‘\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00’
    >> p.value = “Student” # 字符串赋值
    >> p.raw # 后三个字符仍是’ \0 ’
    ‘Student\x00\x00\x00’
    >> p.value = “Big” # 再次赋值
    >> p.raw # 只有前三个字符被修改,第四个字符被修改为’ \0 ’
    ‘Big\x00ent\x00\x00\x00’
  3. ctypes 使用 C 语言指针
    >> i = c_int(999) # 定义 int 类型变量 i,值为 999
    >> pi = pointer(i) # 定义指针,指向变量 i
    >> pi.contents # 打印指针所指的内容
    c_long(999)
    >> pi.contents = c_long(1000) # 通过指针改变变量 i 的值
    >> pi.contents # 打印指针所指的内容
    c_long(1000)
  4. ctypes 使用 C 语言数组和结构体
    >> class POINT(Structure): # 定义一个结构,内含两个成员变量 x,y,均为 int 型
    fields = [(“x”, c_int),
    … (“y”, c_int)]

    >> point = POINT(2,5) # 定义一个 POINT 类型的变量,初始值为 x=2, y=5
    >> print point.x, point.y # 打印变量
    2 5
    >> point = POINT(y=5) # 重新定义一个 POINT 类型变量,x 取默认值
    >> print point.x, point.y # 打印变量
    0 5
    >> POINT_ARRAY = POINT * 3 # 定义 POINT_ARRAY 为 POINT 的数组类型
    >> # 定义一个 POINT 数组,内含三个 POINT 变量
    >> pa = POINT_ARRAY(POINT(7, 7), POINT(8, 8), POINT(9, 9))
    >> for p in pa: print p.x, p.y # 打印 POINT 数组中每个成员的值

    7 7
    8 8
    9 9

Python 访问 C /C++ dll [Windows]

通过 ctypes 模块,Python 程序可以访问 C 语言编译的 dll,这里先在Windows环境下测试Python访问C语言的dll文件,使用Visual Studio 2015编辑器:
创建C程序项目
创建C程序项目
选择Win32控制台应用程序
选择Win32控制台应用程序
设置应用程序向导【C++项目也是雷同的】
设置应用程序向导
这样我们就已经创建好项目了,接下来就需要往项目中添加文件
添加文件
C项目可以不需要写头文件.h,但必须要.c文件
创建C文件
代码:
test01.c

#include <stdio.h>  
#include <stdlib.h>  
__declspec(dllexport) int foo(int a, int b)  //__declspec(dllexport) 必不可少
{
	printf("you input %d and %d\n", a, b);
	return a + b;
}

必要项
必须选择Debug模式,选择X86还是X64需要根据你使用的python的版本是X86还是X64的选择
查看python是x64还是x32
重新生成项目即可得到dll动态文件而不是本地Windows调试项目[前提是要把当前文件所在的项目设置为启动项目]
生成项目
找到dll动态链接库文件
dll文件所在目录
拷贝到要执行的Pycharm项目下:
编写代码:

from ctypes import *
import os
CUR_PATH = os.path.dirname(__file__)
dllC = os.path.join(CUR_PATH, "pythonC.dll")
libc = windll.LoadLibrary(dllC)
print(libc.foo(5, 8))

执行结果
执行结果
强调:【python的版本一定要和dll动态链接库的版本一致】在64位的PYTHON不能调用32位的DLL。
Windows环境下测试Python访问C++的dll文件,项目创建步骤和上面几乎一样,只是文件后缀要是.cpp的,规范和C的也不一样:
不创建空项目是需要stdafx.h文件,空项目没有
不要是空项目

pythontest.h

#pragma once
int sum(int, int);

pythontest.cpp

// pythontest.cpp : 定义 DLL 应用程序的导出函数。
//

#include "stdafx.h"
#define DLLEXPORT extern "C" __declspec(dllexport)

DLLEXPORT int __stdcall sum(int a, int b) {
	return a + b;
}

生成项目的步骤和上一致,得到dll动态链接库
就可以在Pycharm中执行了

from ctypes import *
import os
CUR_PATH = os.path.dirname(__file__)
dllPath = os.path.join(CUR_PATH, "pythontest.dll")
somelibc = WinDLL(dllPath)
print(somelibc.sum(2, 5))

执行代码!
运行结果正确

Python 访问 C /C++ dll [Linux]

在Linux上运行需要gcc(编译C文件) 和g++(编译C++文件)这两个编译工具

安装 gcc g++
  1. 更新系统:apt-get update 【注意:要在root用户下】
  2. sudo apt-get install gcc-4.8 【报错不用管继续下面】
    安装gcc
    这个错误忽略它
  3. ls /usr/bin/gcc*查看所有gcc文件
  4. sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
    更新程序,设置当前执行文件路径
  5. sudo update-alternatives --config gcc
    设置配置文件
    配置gcc成功!
    配置gcc成功!
  6. sudo apt-get install g++
  7. ls /usr/bin/g++*
  8. sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g+±5 100
  9. sudo update-alternatives --config g++
    配置g++成功!
    配置g++成功!

创建test.c文件并编辑代码:
touch test.c
sudo vim test.c
test.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;
}

当前目录命令行执行:gcc test.c -fPIC -shared -o test.so

就会在当前目录中生成test.so文件
在Pycharm中执行代码
test01.py

import ctypes


def main():
    so = ctypes.cdll.LoadLibrary
    lib = so("./test1.so")
    print(lib.foo(8, 5))


if __name__ == '__main__':
    main()

执行成功!
输出结果成功!

创建testc.cpp文件并编辑代码:
touch testc.cpp
sudo vim testc.cpp
testc.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);
    }
}

当前目录命令行执行:g++ -o testc.so -shared -fPIC testc.cpp
就会在当前目录中生成testc.so文件
在Pycharm中执行代码
test02.py

import ctypes
so = ctypes.cdll.LoadLibrary
lib = so("./testc.so")
print('display()')  # display()
lib.display()  # First display
print('display(100)')  # display(100)
lib.display_int(100)  # Second display:2

执行成功!
执行调用c++类库效果

小结:

  1. 要想在Windows和Linux上通过python调用C/C++,需要注意差异性
  2. Windows系统C/C++代码要想被python调用,需要生成dll动态链接库
  3. Linux系统则需要安装gcc和g++编译器,将C/C++代码编译成.so文件
  4. Windows下C代码需要在函数前加__declspec(dllexport)声明可导出,C++代码需要:
    • 导入一个头文件,需要使用__declspec(dllexport)的声明来说明这个函数是导出的
      #include “stdafx.h”
      #define DLLEXPORT extern “C” __declspec(dllexport)
    • 在要导出的函数前添加这个关键字,并添加 __stdcall限定
  5. Linux可以省略__declspec(dllexport)将export指令添加到目标文件中的步骤, 只需要把要导出的类,函数注册进 extern “C”{}即可

Python调用C/C++可执行程序(Windows)

(1)C/C++程序:pythonConsole.cpp [注意:程序中不能有阻塞]

#include "stdafx.h"
#include <iostream>

int add_func(int a, int b)
{
	return a + b;
}

int main()
{
	std::cout << "the C/C++ run result:" << std::endl;
	std::cout << add_func(2, 3) << std::endl;
    return 0;
}

(2)用命令行或者IDE编译成exe等执行文件

import os

def main():
    cpptest = "pythonConsole.exe" 
    if os.path.exists(cpptest):
        f = os.popen(cpptest)
        data = f.readlines()
        f.close()
        print(data)
    print("python execute cpp program:")
    os.system(cpptest)
if __name__ == '__main__':
    main()

执行成功!
执行文件

Python调用C/C++可执行程序(Linux)

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

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

(2)编译成二进制可执行文件:g++ -o testmain main.cpp。
(3)Python调用程序:main.py
(3)Python调用程序:test.py [pythonConsole.exe文件和他同目录]

import subprocess
import os

def main():
    test2 = "./test2"
    if os.path.exists(test2):
        rc, out = subprocess.getstatusoutput(test2)
        print('rc = %d, \nout = %s' % (rc, out))

    print('*' * 10)
    f = os.popen(test2)
    data = f.readlines()
    f.close()
    print(data)

print('*' * 10)
os.system(test2)  

执行成功!
执行效果
思考:
为什么我用mydll=ctypes.cdll.LoadLibrary(dllPath)不成功,而用pDll=ctypes.WinDLL(dllPath)则可以成功?
答:在Windows上,cdll.LoadLibrary将在CWD【目前的工作目录】中搜索DLL 。在Linux上,有必要提供路径【绝对路径】windll只是WinDLL的一个类。

拓展:
  1. 编译成so库[C程序拓展python模块]:g++ -fPIC test.cpp -o example.so -shared -I/usr/include/python3.5.2 -I/usr/lib/python3.5.2/config
  2. 调用 C++Boost.Python:g++ hello.cpp -o hello.so -shared -I/usr/include/python3.5 -I/usr/lib/python3.5/config -lboost_python-gcc42-mt-1_34_1【需要安装包:apt-get install libboost-python-dev】

总结

(1)在软件开发过程中同时运用 Python 语言和 C 语言,既能够在加快开发速度的同时,也能够保证软件的运行性能。
(2)两者交互,C++可为Python编写扩展模块,Python也可为C++提供脚本接口,更加方便于实际应用

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值