利用Python CFFI进行原子级别的C库调用

发布时间:2023-05-10

介绍

Python是一种高级语言,常用于快速开发、数据挖掘等领域,但有时候需要借助C库进行密集计算等操作。Python提供了很多种方式进行C库调用,例如ctypes、Swig等,但各种方式都存在一些问题。CFFI是Python官方推荐的C库调用方式,提供了原子级别的C库调用能力,一致性强,灵活性高,效率较高,已被广泛应用于NumPy、PyPy、Pillow等多个Python库。

正文

一、CFFI的基础使用

使用CFFI调用C库的过程包括三步:

  1. 定义C函数接口
    from cffi import FFI
    ffi = FFI()
    lib = ffi.dlopen('libc.so.6')
    # 定义C函数接口
    # void 则表示没有返回值
    # int 则表示返回值类型是整数
    # int, int 表示C函数接收两个整数类型的参数
    add = lib.printf
    add.argtypes = ffi.typeof("char *")
    add.restype = ffi.typeof("int")
    
  2. 调用C函数接口
    # 调用C函数接口
    result = add(b"Hello World")
    print(result)
    
  3. 编译代码
    gcc -shared -o libtest.so test.c
    

二、CFFI与ctypes的比较

ctypes是Python内置的C库调用方式,也是使用比较广泛的一种方式,但与CFFI相比还是存在一些差异:

  • 使用方式:CFFI要求对C语言的代码重构最小,而ctypes则要求用户按照ctypes的规范来组织C代码。
  • 兼容性:CFFI兼容性良好,支持多个平台,而ctypes不一定能够在所有平台上正常使用。
  • Python版本:CFFI仅支持Python2.6、Python2.7、Python3.2以及更高版本,而ctypes作为Python内置库则兼容Python2.6 ~ Python3.x各版本。

三、CFFI的高级使用

除了基本的C库调用之外,CFFI还提供了一些高级功能,例如:

Struct类型的支持

from cffi import FFI
ffi = FFI()
cpp_code = '''
#include <stdio.h>
#include <stdint.h>
struct PointF {
    float x;
    float y;
};
typedef struct PointF PointF;
void show_point(PointF p) {
    printf("%0.1f, %0.1f", p.x, p.y);
}'''
# 声明C数据结构
ffi.cdef("""
    typedef struct {
        float x;
        float y;
    } PointF;
    void show_point(PointF);
""")
lib = ffi.verify(cpp_code, libraries=[])
p = ffi.new('PointF*', [1.5, 2.4])
lib.show_point(p[0])

Union类型的支持

from cffi import FFI
ffi = FFI()
cpp_code = '''
#include <stdio.h>
union Shape {
    int shape_type;
    struct {
        int x;
        int y;
        int width;
        int height;
    } rect;
};
typedef union Shape Shape;
void show_rect(Shape r) {
    printf("(%d, %d, %d, %d)",
        r.rect.x, r.rect.y, r.rect.width, r.rect.height);
}'''
ffi.cdef("""
    typedef union {
        int shape_type;
        struct {
            int x;
            int y;
            int width;
            int height;
        } rect;
    } Shape;
    void show_rect(Shape);
""")
lib = ffi.verify(cpp_code, libraries=[])
s = ffi.new('Shape*', [0])
s.rect.x = 1
s.rect.y = 2
s.rect.width = 3
s.rect.height = 4
lib.show_rect(s[0])

代码部分

以下是一个使用CFFI调用Windows API的例子:

# -*- coding: utf-8 -*-
from cffi import FFI
# 定义C函数接口
ffi = FFI()
ffi.cdef("""
    typedef struct _FILETIME {
        unsigned long dwLowDateTime;
        unsigned long dwHighDateTime;
    } FILETIME, *PFILETIME, *LPFILETIME;
    typedef struct _SYSTEMTIME {
        short wYear;
        short wMonth;
        short wDayOfWeek;
        short wDay;
        short wHour;
        short wMinute;
        short wSecond;
        short wMilliseconds;
    } SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;
    void GetSystemTime(SYSTEMTIME *lpSystemTime);
    void SystemTimeToFileTime(const SYSTEMTIME *lpSystemTime, LPFILETIME lpFileTime);
    void FileTimeToLocalFileTime(CONST FILETIME *lpFileTime, LPFILETIME lpLocalFileTime);
    BOOL FileTimeToSystemTime(const FILETIME *lpFileTime, LPSYSTEMTIME lpSystemTime);
""")
lib = ffi.dlopen('kernel32.dll')
# 调用GetSystemTime函数,获取当前系统时间(UTC)
system_time = ffi.new('SYSTEMTIME *')
lib.GetSystemTime(system_time)
# 将UTC时间转换为本地时间
file_time_utc = ffi.new('FILETIME *')
lib.SystemTimeToFileTime(system_time, file_time_utc)
file_time_local = ffi.new('FILETIME *')
lib.FileTimeToLocalFileTime(file_time_utc, file_time_local)
# 获取系统时间(本地时间)
local_time = ffi.new('SYSTEMTIME *')
lib.FileTimeToSystemTime(file_time_local, local_time)
print('Local Time:', local_time.wYear, local_time.wMonth, local_time.wDay, local_time.wHour, local_time.wMinute, local_time.wSecond, local_time.wMilliseconds)