Python3调用C程序(超详解)

Python为什么要调用C?

1.要提高代码的运算速度,C比Python快50倍以上

2.对于C语言里很多传统类库,不想用Python重写,想对从内存到文件接口这样的底层资源进行访问

Python调用C的方法:

Python调用C的方法通常有3种:

1.SWIG,编写一个额外的接口文件来作为SWIG(终端工具)的入口

2.通过CTypes调用

3.使用Python/C API方法

第一种方法大多数情况下会带来不必要的麻烦,我并没有试验,本文只针对2,3方法作详细说明

通过CTypes调用:

Python中的ctypes模块可能是Python调用C方法中最简单的一种。ctypes模块提供了和C语言兼容的数据类型和函数来加载dll文件,因此在调用时不需对源文件做任何的修改。也正是如此奠定了这种方法的简单性。

下面是python文件的代码:

  1. from ctypes import * #pip ctypes库,并导入库
  2. test = CDLL("./test.dll") #调用当前目录下叫test的dll文件,dll文件是C生成的动态链接库
  3. result =test.sum(5,10) #调用库里的函数sum,求和函数
  4. print(result) #打印结果

接下来用C语言编写dll动态链接库,这里使用VS:



单击头文件,新建项:

添加源文件:

在头文件test.h中加入如下代码:

  1. #pragma once
  2. #ifdef BUILD_TEST
  3. #define API_SYMBOL __declspec(dllexport)
  4. #else
  5. #define API_SYMBOL __declspec(dllimport)
  6. #endif
  7. //宏定义,导出或者导入//
  8. extern "C" API_SYMBOL int sum(int x, int y);
  9. //导出函数//

在源文件test.cpp中加入如下代码:

  1. #define BUILD_TEST //使导出函数生效
  2. #include "test.h"
  3. #include "stdio.h"
  4. #include "pch.h"
  5. int sum(int a, int b) {
  6. return a + b;
  7. }

注意,这里要点击64位,再点击生成(因为目前大部分电脑安装Python解释器是64位的,否则默认生成32位的动态库,会导致无法调用)这是一个很隐蔽的坑!!!

在生成的动态库路径下找到test.dll文件,并复制到python项目下

最后在python中运行代码,出现如下问题:

为了寻找问题,我将python文件中的代码替换如下,发现调用动态库是成功的,只是不能调用动态库里面的函数

  1. from ctypes import *
  2. from ctypes import * #pip ctypes库,并导入库
  3. test = CDLL("./test.dll") #调用当前目录下叫test的dll文件,dll文件是C生成的动态链接库
  4. print("加载成功")
  5. # result =test.sum(5,10) #调用库里的函数sum,求和函数
  6. # print(result) #打印结果

以上流程我是参考B站一位UP主的具体教学,但还是行不通,于是我将test.cpp源文件中的代码更改如下:

  1. #define BUILD_TEST
  2. #include "test.h"
  3. #include "stdio.h"
  4. #include "pch.h"
  5. #define DLLEXPORT extern "C" __declspec(dllexport) //直接在源文件定义导出
  6. DLLEXPORT int sum(int a, int b) {
  7. return a + b;
  8. }//两数相加

重复上述流程,生成dll文件,将文件放置于python项目中,然后调用,终于成功

  1. from ctypes import *
  2. from ctypes import * #pip ctypes库,并导入库
  3. test = CDLL("./test.dll") #调用当前目录下叫test的dll文件,dll文件是C生成的动态链接库
  4. print("加载成功")
  5. result =test.sum(5,10) #调用库里的函数sum,求和函数
  6. print(result) #打印结果

使用Python/C API方法:

Python/C API可能是被最广泛使用的方法。它不仅简单,而且可以在C代码中操作你的Python对象。但要使用这种方法,需要用特定的方式来编写C代码,所以C代码不是原生的C(大伙要适应一下),这样才可以供python去调用。这里参照python进阶的说明博客园的一篇文章

python文件的代码如下:

  1. import Test
  2. x = 1
  3. print(Test.add_one(x))

而这里的Test模块,则是需要我们自己用C语言写,C文件代码如下:

  1. #include <Python.h>
  2. int add_one(int a){
  3. return a + 1;
  4. }//这是C的原生函数,实现+1功能
  5. static PyObject * py_add_one(PyObject *self, PyObject *args){
  6. int num;
  7. if (!PyArg_ParseTuple(args, "i", &num)) return NULL;
  8. return PyLong_FromLong(add_one(num));
  9. }//这一串代码要实现的功能如下,按照python规定的调用方式:
  10. //1.定义一个新的静态函数,接收2个PyObject *参数,返回1个PyObject *值
  11. //2.PyArg_ParseTuple方法将python输入的变量变成C的变量,即上述args→num
  12. //3.紧接着调用C原生函数add_one,传入num
  13. //4.最后将调用返回的C变量,转换为PyObject*或其子类,并通过PyLong_FromLong方法,返回python值
  14. static PyMethodDef Methods[] = {
  15. {"add_one", py_add_one, METH_VARARGS},
  16. {NULL, NULL}
  17. };//这串代码的目的是创建一个数组,来指明Python可以调用这个扩展函数:
  18. //其中"add_one",代表编译后python调用时希望使用的函数名。而py_add_one,代表要调用当前C代码中的哪一个函数,即static PyObject * py_add_one。METH_VARARGS,代表函数的参数传递形式,主要包括位置参数和关键字参数两种
  19. //最后如果希望添加新的函数,则在最后的{NULL, NULL}里按同样格式填写新的调用信息,比如加一些求和求乘积函数
  20. static struct PyModuleDef cModule = {
  21. PyModuleDef_HEAD_INIT,
  22. "Test", /*模块名,即是生成模块后的名字,如numpy,opencv等等*/
  23. "", /* 模块文档,可以为空NULL */
  24. -1, /* 模块中每个解释器的状态大小,模块在全局变量中保持状态,则为-1 */
  25. Methods//即上一步定义的Methods,说明了可调用的函数集
  26. };//创建module的信息
  27. PyMODINIT_FUNC PyInit_Test(void){ return PyModule_Create(&cModule);}
  28. //module初始化

将C文件放置在python项目的同文件目录下,然后编写setup.py文件,setup.py是用来将C打包成模块的脚本文件,代码如下:

  1. from distutils.core import setup, Extension #这里要用到distutils库
  2. module1 = Extension('Test', sources = ['test.c']) #打包文件为test.c,这里没有设置路径,所有.c文件和setup.py放在同一目录下
  3. setup (name = 'Test', #打包名
  4. version = '1.0', #版本
  5. description = 'This is a demo package', #说明文字
  6. ext_modules = [module1])

然后在pycharm的Terminal终端输入(因为这里用的python3.5+,windows平台下python的C/C++扩展不再支持gcc的编译,并强制要求使用msvc进行编译,如果版本低于3.5,用python setup.py build这是一个隐藏的坑!!!用python setup.py build一直出问题

  1. python setup.py build --compiler msvc #编译代码
  2. python setup.py build --compiler msvc install #编译代码并直接将包放入当前python环境的包的路径以供调用

最后结果,顺利打包,我们在python里安装了自己的包并完成调用:

本文由博客一文多发平台 OpenWrite 发布!

Python3调用C程序(超详解)的更多相关文章

  1. Linux Bash命令关于程序调试详解

    转载:http://os.51cto.com/art/201006/207230.htm 参考:<Linux shell 脚本攻略>Page22-23 Linux bash程序在程序员的使 ...

  2. UIViewController中各方法调用顺序及功能详解

    UIViewController中各方法调用顺序及功能详解 UIViewController中loadView, viewDidLoad, viewWillUnload, viewDidUnload, ...

  3. 一个简单的C语言程序(详解)

    C Primer Plus之一个简单的C语言程序(详解) #include <stdio.h> int main(void) //一个简单的 C程序 { int num; //定义一个名为 ...

  4. Python调用windows下DLL详解

    Python调用windows下DLL详解 - ctypes库的使用 2014年09月05日 16:05:44 阅读数:6942 在python中某些时候需要C做效率上的补充,在实际应用中,需要做部分 ...

  5. JUC中的AQS底层详细超详解

    摘要:当你使用java实现一个线程同步的对象时,一定会包含一个问题:你该如何保证多个线程访问该对象时,正确地进行阻塞等待,正确地被唤醒? 本文分享自华为云社区<JUC中的AQS底层详细超详解,剖 ...

  6. 嵌入式Linux应用程序开发详解------(创建守护进程)

    嵌入式Linux应用程序开发详解 华清远见 本文只是阅读文摘. 创建一个守护进程的步骤: 1.创建一个子进程,然后退出父进程: 2.在子进程中使用创建新会话---setsid(): 3.改变当前工作目 ...

  7. VS2010开发程序打包详解

    VS2010开发程序打包详解 转自:http://blog.sina.com.cn/s/blog_473b385101019ufr.html 首先打开已经完成的工程,如图: 下面开始制作安装程序包. ...

  8. Thrift实现C#调用Java开发步骤详解

    概述 Thrift实现C#调用Java开发步骤详解 详细 代码下载:http://www.demodashi.com/demo/10946.html Apache Thrift 是 Facebook ...

  9. html5的float属性超详解(display,position, float)(文本流)

    html5的float属性超详解(display,position, float)(文本流) 一.总结 1.文本流: 2.float和绝对定位都不占文本流的位置 3.普通流是默认定位方式,就是依次按照 ...

随机推荐

  1. 2021.8.12考试总结[NOIP模拟37]

    T1 数列 考场上切掉的简单题. $a$,$b$与数列中数的正负值对答案无关.全当作正数计算即可. $exgcd$解未知数系数为$a$,$b$,加和为$gcd(a,b)$的不定方程组,再枚举每个数.如 ...

  2. 数组中的逆序对 牛客网 剑指Offer

    数组中的逆序对 牛客网 剑指Offer 题目描述 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数P.并将P对10000000 ...

  3. 深入剖析Redis客户端Jedis的特性和原理

    一.开篇 Redis作为目前通用的缓存选型,因其高性能而倍受欢迎.Redis的2.x版本仅支持单机模式,从3.0版本开始引入集群模式. Redis的Java生态的客户端当中包含Jedis.Rediss ...

  4. MarkDown学习随笔

    MarkDown语法的学习 标题 ​ 设置标题方法是在前面加#号,一级标题(最大)是加#+空格 ,二级标题是加##+空格,之后的以此类推. 字体 在文本的前后分别加上一个星号表示斜体字 在文本的前后分 ...

  5. python3 调用 centos 常用系统命令

    一.创建目录 1 import os 2 3 base_path = '/data/sw_backup' 4 addr= 'FT' 5 ip='192.168.1.1' 6 path = base_p ...

  6. 谷粒 | 项目集成redis

    添加依赖 由于redis缓存是公共应用,所以我们把依赖与配置添加到了common模块下面,在common模块pom.xml下添加以下依赖 <!-- redis --> <depend ...

  7. JS数据类型转换问题

    一.数据类型的转换 数据类型的转换方法 强制转换(显示转换,主动转换) 字符转数值 parseInt(要转换的数值或变量) 转整数 从左向右依次转换,遇到第一个非数字的字符,停止转换 忽略小数点后的内 ...

  8. 讲分布式唯一id,这篇文章很实在

    分布式唯一ID介绍 分布式系统全局唯一的 id 是所有系统都会遇到的场景,往往会被用在搜索,存储方面,用于作为唯一的标识或者排序,比如全局唯一的订单号,优惠券的券码等,如果出现两个相同的订单号,对于用 ...

  9. tomcat9启动报错too low setting for -Xss

    在tomcat下部署war包启动时报错,关键错误信息如下: Caused by: java.lang.IllegalStateException: Unable to complete the sca ...

  10. 【JAVA】笔记(3)---封装;如何选择声明静态变量还是实例变量;如何选择声明静态方法还是实例方法;静态代码块与实例代码块的执行顺序与用途;

    封装: 1.目的:保证对象中的实例变量无法随意修改/访问,只能通过我们自己设定的入口,出口(set / get)来间接操作:屏蔽类中复杂的结构,使我们程序员在主方法中关联对象写代码时,思路/代码格式更 ...