Python调用C/C++动态链接库

最近需要使用Python调用C/C++功能,于是进行了一些相关调研。总体来说,Python调用C功能还算是相对比较简单,主要涉及ctypes这个函数库。

ctypes 是 Python 的外部函数库。它提供了与 C 兼容的数据类型,并允许调用C共享库中的函数。可使用该模块以纯 Python 形式对这些库进行封装。

基本数据类型的使用

ctypes定义了一些与C兼容的数据类型:

本篇主要关注跨语言调用时指针的处理方法,对于更全面的介绍,可参考:ctypes — Python 的外部函数库 — Python 3.7.13 文档

下面用一个小例子来介绍Python调用C/C++动态库的方法。

C代码:

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

#ifdef __cplusplus
extern "C"
{
#endif

using namespace std;

// test for input char pointer
void print_string(char* str)
{
    printf("This is c code: print_string().\n");
    printf("%s\n", str);
}

// test for input integers and return an integer
int add_func(int a, int b)
{
    printf("This is c code: add_func()\n");
    return a + b;
}

// test for pointer as return value
int* get_array()
{
    int *pa = new int[10];
	for(int i = 0; i < 10; i++)
	{
			pa[i] = i;
	}
	return pa;
}

void free_array(int *pa)
{
	if(pa)
		delete [] pa;

}

#ifdef __cplusplus
}
#endif

上面测试代码定义了普通整数的计算并返回整数、输入字符指针并打印、输出整型指针、释放指针等操作。需要注意的是,由于ctypes只与C兼容,而C++因为支持函数重载而在编译时修改函数名,因此,对于C++代码,需要使用C的方式编译。不了解的同学可自行搜索extern "C"的用法,本篇不做过多展开。

将以上代码编译成动态链接库:

g++ -std=c++11 test_c.c -shared -fPIC -o test_c.so

接着,我们使用Python来调用该动态库。Python代码:

#!/usr/bin/python

from ctypes import *
import os

# Load dynamic library
#lib_path = os.getcwd() + '/test_c.so'
lib_path = './test_c.so'
solib = cdll.LoadLibrary(lib_path)

# Indicate the function arguments type and return value type
solib.print_string.argtypes = [c_char_p]
solib.print_string.restype = c_void_p

# Call print_string function in C library
solib.print_string(b"Hello Python!")

# Call add function in C library
solib.add_func.argtypes = [c_int, c_int]
solib.add_func.restype = c_int
sum = solib.add_func(100,200)
print('Python code: sum = {}'.format(sum))


# Call get_array function in C library, the return value is a pointer of integer
solib.get_array.restype = POINTER(c_int)
p_array = solib.get_array()
int_array = [p_array[i] for i in range(10)]

print("Python code: ")
for x in int_array:
    print(x, end = ' ')

# Free the pointer
solib.free_array.argtypes = [POINTER(c_int)]
solib.free_array.restype = c_void_p
solib.free_array(p_array)

print('\nEnd Python')

运行结果:

在Python代码调用C动态库时,C库函数的参数和返回值必须是ctypes类型,参数类型使用关键字argtypes定义,对参数的定义必须是以序列的形式,如上面代码中的参数类型定义:

solib.add_func.argtypes = [c_int, c_int]

solib.print_string.argtypes = [c_char_p]

返回值参数类型使用restype定义,如上面代码中的语句:

solib.add_func.restype = c_int

solib.get_array.restype = POINTER(c_int)

其中,get_array函数的返回值使用POINTER(c_int)关键字定义成了int型的指针。

用户自定义类型的使用

除了基本数据类型,用户还可以使用自定义类型,下面给出一个自定义结构体的测试例子:

C代码:

typedef struct _point
{
        int x;
        int y;
        char desc[50];
}Point;

int get_point(Point point)
{
        printf("x = %d, y = %d, desc = %s\n", point.x, point.y, point.desc);
        return 0;
}

Python代码:

class Point(Structure):
        _fields_ = [
                ("x", c_int),
                ("y", c_int),
                ("desc", c_char * 50)
        ]


pt = Point(5, 10, b'I am a point.')
print(pt.x, pt.y, pt.desc)

solib.get_point.argtypes = [Point]
solib.get_point.restype = c_int


solib.get_point(pt)

Python中定义结构体时,必须继承Structure类,其成员的定义必须使用_fields_属性,否则无法调用C结构体。_fields_属性是一个list,其成员均为2个值的tuple,分别对应结构体成员的名称(C结构体成员名称)和类型,类型为ctypes类型,或者是由ctypes组合而成的新类型(如自己定义的结构体)。

以上代码的运行结果:

 

来源:牧羊女说

物联沃分享整理
物联沃-IOTWORD物联网 » Python调用C/C++动态链接库

发表评论