Cython学习笔记2:Cython调用C语言函数库

发布于:2025-02-26 ⋅ 阅读:(11) ⋅ 点赞:(0)

Cython

Cython 是一个用于将 Python 代码转换为 C 语言扩展模块的编程语言。它允许你在 Python 中编写 C 风格的代码,从而提高性能,尤其是在需要大量计算的情况下。通过将 Python 代码与 C 代码混合使用,Cython 既保留了 Python 的简洁性,又能提升程序的执行速度。

Cython 的核心特点:

  1. 提高性能:Cython 可以将 Python 代码编译成 C 语言代码,然后生成高效的 C 扩展模块。它适用于需要频繁计算的任务,尤其是数值计算和循环处理。
  2. 与 C 代码交互:Cython 允许直接调用 C 函数和访问 C 数据结构,从而减少了 Python 和 C 之间的接口调用开销。
  3. 简化 C 接口:与直接使用 C 语言不同,Cython 通过 Python 风格的语法简化了 C 接口的使用,开发者可以快速将 Python 代码提升到 C 代码的性能。
  4. 无缝集成:Cython 代码可以与现有的 Python 代码无缝集成,开发者不需要重写整个程序,通常只需要优化性能瓶颈部分。

Cython调用C语言函数库

以Windows操作系统举例,使用Visual Studio 2022创建C语言函数库

1. Cython调用C语言函数库原理

Cython加速Python运行速度原理是:把 python 代码转化为 c 语言代码,然后再编译成动态链接库,最后使用 python 程序调用这个库,示意图如下
在这里插入图片描述
由于是利用 Cython 编译出的动态链接库,因此 python 程序可以直接调用,但是如果调用外部的(不是Cython 编译出的)动态链接库,需要执行如下过程:

在这里插入图片描述
把 .pyx 文件,文件中定义了要使用的外部库中的函数,然后转化为 c 语言代码,然后再编译成动态链接库,最后使用 python 程序调用这个库

2. 创建C语言动态链接库

打开VS2022,选择创建一个新的项目
在这里插入图片描述
找到动态链接库
在这里插入图片描述
填写合适的项目名称和路径
在这里插入图片描述
把原来项目中的cpp文件修改c文件,然后创建一个要调用的函数头文件和.c文件,原来项目中的文件不进行任何修改
在这里插入图片描述
DllMain 是 DLL 的入口函数,每次 DLL 被加载或卸载时,都会调用这个函数。它有三个参数:

  • HMODULE hModule:该参数是 DLL 模块的句柄,表示当前加载的 DLL。通过这个句柄可以访问 DLL 的信息。
  • DWORD ul_reason_for_call:表示触发 DllMain 的原因,可能的值有:
    1.DLL_PROCESS_ATTACH:DLL 被加载到进程中时。
    2.DLL_THREAD_ATTACH:线程被创建时。
    3.DLL_THREAD_DETACH:线程被销毁时。
    4.DLL_PROCESS_DETACH:DLL 被卸载时。
  • LPVOID lpReserved:保留参数,通常在处理过程中没有使用,但它允许未来的扩展。

fun.h内容如下

#ifndef __FUN_H__
#define __FUN_H__
__declspec(dllexport) double calculate_pi(int count);
#endif

fun.c内容如下

#include "pch.h"
#include "fun.h"

__declspec(dllexport) double calculate_pi(int count) {
    double pi = 3.0;
    int sign = 1;
    for (int i = 2; i < count * 2; i += 2) {
        double term = sign * 4.0 / (i * (i + 1) * (i + 2));
        pi += term;
        sign *= -1;
    }
    return pi;
}

这个函数用来计算圆周率 π,使用Nilakantha 级数来计算 π 值,计算公式如下:
在这里插入图片描述

接下来建立动态链接库,选择Build -> Build DLLTest

在这里插入图片描述
信息如下展示:
在这里插入图片描述
之后进入到项目所在的目录,进入x64/Debug/目录下,就可以看到创建的dll了
在这里插入图片描述

2. 使用Cython在Python中使用DLL

这里使用的是Pycharm创建了一个项目,把之前产生的.lib和.dll复制过来,写的fun的代码复制过来

创建 fun_wrapper.pyx文件,代码如下

# cython: language_level=3
cdef extern from "./fun.h":
    cpdef double calculate_pi(int count)

这里只需要写需要调用的函数就可以了

Cython 语言使用标准的 C 语法来表示 C 类型,包括指针。它提供了所有标准的 C 类型,详细可以查看这个链接

Cython 语言使用的 C 语言标准

在这里插入图片描述
创建 setup.py文件,代码如下,用于链接外部的库

from Cython.Build import cythonize
from setuptools import Extension, setup


extern = Extension(
    "fun", ["fun_wrapper.pyx"],
    libraries=["DLLTest"], library_dirs=["./"]
)

setup(name='fun_wrapper', ext_modules=cythonize(extern))

在此目录下,执行命令

python setup.py build_ext --inplace

执行成功后,就会在当前目录下面出现 .pyd 文件

在这里插入图片描述
创建一个 main.py,测试一下,代码如下:

import time
import os

# 将当前目录添加到 PATH 中
os.environ['PATH'] = os.getcwd() + os.pathsep + os.environ['PATH']
import fun


def calculate_pi(count: int) -> float:
    pi = 3.0
    sign = 1
    for i in range(2, count * 2, 2):
        term = sign * 4.0 / (i * (i + 1) * (i + 2))
        pi += term
        sign *= -1
    return pi


def main():
    start_time = time.time()
    print(calculate_pi(20_000_000))
    end_time = time.time()
    print('Time:', end_time - start_time)

    start_time = time.time()
    print(fun.calculate_pi(20_000_000))
    end_time = time.time()
    print('Time:', end_time - start_time)


if __name__ == '__main__':
    main()

运行结果如下:

3.141592653589787
Time: 4.7831432819366455
3.49635588787317
Time: 0.159468412399292

可以看到外部库在python中调用成功了,而且运行速度比在python中定义的函数要快

如果是把c++文件打包成动态链接库,不要忘记在函数最前面,加入 extern "C"

如下fun.cpp

#include "pch.h"
#include "fun.h"

extern "C" __declspec(dllexport) double calculate_pi(int count) {
    double pi = 3.0;  
    int sign = 1;     
    for (int i = 2; i < count * 2; i += 2) {
        double term = sign * 4.0 / (i * (i + 1) * (i + 2));
        pi += term;
        sign *= -1; 
    }
    return pi;
}