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 type | c type | Python type |
---|---|---|
c_char | char | 1-character string |
c_wchar | wchar_t | 1-character unicode string |
c_byte | char | int/long |
c_ubyte | unsigned char | int/long |
c_short | short | int/long |
c_ushort | unsigned short | int/long |
c_int | int | int/long |
c_uint | unsigned int | int/long |
c_long | long | int/long |
c_ulong | unsigned long | int/long |
c_longlong | __int64 or long long | int/long |
c_ulonglong | unsigned __int64 or unsigned long long | int/long |
c_float | float | float |
c_double | double | float |
c_char_p | char * (NUL terminated) | string or None |
c_wchar_p | wchar_t * (NUL terminated) | unicode or None |
c_void_p | void * | int/long or None |
表中的第一列是在 ctypes 库中定义的变量类型,第二列是 C 语言定义的变量类型,第三列是 Python 语言在不使用 ctypes 时定义的变量类型。
- ctypes简单使用:
>> from ctypes import * # 导入 ctypes 库中所有模块
>>i = c_int(45) # 定义一个 int 型变量,值为 45
>>i.value # 打印变量的值
45
>>i.value = 56 # 改变该变量的值为 56
>>i.value # 打印变量的新值
56 - 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’ - 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) - 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程序项目
选择Win32控制台应用程序
设置应用程序向导【C++项目也是雷同的】
这样我们就已经创建好项目了,接下来就需要往项目中添加文件
C项目可以不需要写头文件.h,但必须要.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的选择
重新生成项目即可得到dll动态文件而不是本地Windows调试项目[前提是要把当前文件所在的项目设置为启动项目]
找到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++
- 更新系统:apt-get update 【注意:要在root用户下】
- sudo apt-get install gcc-4.8 【报错不用管继续下面】
- ls /usr/bin/gcc*
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
- sudo update-alternatives --config gcc
配置gcc成功!
- sudo apt-get install g++
- ls /usr/bin/g++*
- sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g+±5 100
- sudo update-alternatives --config 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
执行成功!
小结:
- 要想在Windows和Linux上通过python调用C/C++,需要注意差异性
- Windows系统C/C++代码要想被python调用,需要生成dll动态链接库
- Linux系统则需要安装gcc和g++编译器,将C/C++代码编译成.so文件
- Windows下C代码需要在函数前加__declspec(dllexport)声明可导出,C++代码需要:
- 导入一个头文件,需要使用__declspec(dllexport)的声明来说明这个函数是导出的
#include “stdafx.h”
#define DLLEXPORT extern “C” __declspec(dllexport) - 在要导出的函数前添加这个关键字,并添加 __stdcall限定
- 导入一个头文件,需要使用__declspec(dllexport)的声明来说明这个函数是导出的
- 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的一个类。
拓展:
- 编译成so库[C程序拓展python模块]:g++ -fPIC test.cpp -o example.so -shared -I/usr/include/python3.5.2 -I/usr/lib/python3.5.2/config
- 调用 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++提供脚本接口,更加方便于实际应用