C语言编写CPython扩展库
很多时候C语言的效率要比Python高很多,所以通过C编写python扩展库在处理能力不太强劲的芯片上将有较好的效果。
扩展库的编写
下面通过一个小Demo工程描述扩展库是怎么配置编译的,在分享编写过程前先介绍一下工程的目录结构
工程目录结构
在libs文件夹下建立了一个与python库同级的文件夹。
ext_module_example/
├── ext_setup.py
└── test_example
├── extAPI.c
├── extAPI.c.autosave
├── include
│ └── Extest.h
└── src
└── Extest.c
这个文件夹的顶层目录存在一个ext_setup.py
的配置文件,这个文件的编写方法在后边会提到。
Demo工程文件都放在test_example
中。
在test_example
这个文件夹的顶层目录存在一个extAPI.c
的文件,这个文件的目的是为python编写接口,转换数据类型等。
在test_example
这个文件夹的目录下还存在两个文件夹,include
文件夹用来存放头文件,src
文件夹用来存放源代码文件。
C工程源码
Extest.c
#include "string.h"
#include "Extest.h"
void test_print(void)
{
printf("c to python test test_print\n");
}
int test_plus(int a, int b)
{
printf("c to python test test_plus %d + %d = %d\n", a, b, a+b);
return a+b;
}
Extest.h
#ifndef __EXTEST_
#define __EXTEST_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void test_print(void);
int test_plus(int a, int b);
#endif
extAPI.c
#include <Python.h>
#include "Extest.h"
static PyObject*
extest_test_print(PyObject *self, PyObject *args)
{
test_print();
return (PyObject *)Py_BuildValue("");
}
static PyObject *
extest_test_plus(PyObject *self, PyObject *args)
{
int a, b; //参数
// 判断输入的参数是否为整型, 如果不是返回一个NULL值, i 表示整型, &num为输入参数
if(!(PyArg_ParseTuple(args, "ii", &a, &b))){
return NULL;
}
// 把C函数中计算的结果转换成Python对象并返回
return (PyObject *)Py_BuildValue("i", test_plus(a, b));
}
/* 记录函数信息, {函数在Python中名称, 函数对应封装, 参数格式(此处表示参数以元组格式传入)} */
static PyMethodDef
ExtestMethods[] =
{
{"test_plus", extest_test_plus, METH_VARARGS},
{"test_print", extest_test_print, METH_VARARGS},
{NULL, NULL, 0, NULL}
};
/* Module structure */
static struct PyModuleDef Extestmodule = {
PyModuleDef_HEAD_INIT,
"myext", /* 库名称 */
"A sample module", /* Doc string (may be NULL) */
-1, /* Size of per-interpreter state or -1 */
ExtestMethods /* 函数信息 */
};
PyMODINIT_FUNC PyInit_myext(void) { /* PyInit_库名称 */
return PyModule_Create(&Extestmodule); /* 参数为Module structure名词 */
}
配置文件的编写
ext_setup.py
ext_setup.py
这个文件定义了许多对象,对编译的路径和编译的模块进行声明。
from distutils.core import Extension, setup
import os
#因为源文件路径使用相对路径不安全,所以通过cur_dir指定绝对路径
cur_dir = os.path.abspath(os.path.dirname(__file__)) + '/'
#为setup.py提供编译数据
luna_ext = []
luna_srcdir = []
luna_incdir = []
luna_libdir = []
#源文件路径
#等同于"./test_example/src"
luna_srcdir.append(os.path.join(cur_dir, 'test_example', 'src'))
#等同于"./test_example"
luna_srcdir.append(os.path.join(cur_dir, 'test_example'))
#头文件路径
luna_incdir.append(os.path.join(cur_dir, 'test_example', 'include'))
#外部库
luna_ext.append(Extension("myext", # 完整扩展名(可能含有.),与extAPI.c中的库名称对应起来
sources=['extAPI.c','Extest.c'], #源文件,需要指定所有源文件
include_dirs=luna_incdir, #指定头文件路径
#library_dirs=luna_libdir, #指定库路径
) )
Extension这个接口是用来配置扩展库的,此接口可以配置很多参数,其中有两个位置参数(name和sources)是必选参数,其他作为默认参数,可以通过传入关键字参数的方式配置。
特别注意:sources需要指定工程中编写的所有的C文件,不可以只指定Python接口文件
在源码extension.py中可以看得看到更详细信息。
class Extension:
"""Just a collection of attributes that describes an extension
module and everything needed to build it (hopefully in a portable
way, but there are hooks that let you be as unportable as you need).
Instance attributes:
name : string
the full name of the extension, including any packages -- ie.
*not* a filename or pathname, but Python dotted name
sources : [string]
list of source filenames, relative to the distribution root
(where the setup script lives), in Unix form (slash-separated)
for portability. Source files may be C, C++, SWIG (.i),
platform-specific resource files, or whatever else is recognized
by the "build_ext" command as source for a Python extension.
include_dirs : [string]
list of directories to search for C/C++ header files (in Unix
form for portability)
define_macros : [(name : string, value : string|None)]
list of macros to define; each macro is defined using a 2-tuple,
where 'value' is either the string to define it to or None to
define it without a particular value (equivalent of "#define
FOO" in source or -DFOO on Unix C compiler command line)
undef_macros : [string]
list of macros to undefine explicitly
library_dirs : [string]
list of directories to search for C/C++ libraries at link time
libraries : [string]
list of library names (not filenames or paths) to link against
runtime_library_dirs : [string]
list of directories to search for C/C++ libraries at run time
(for shared extensions, this is when the extension is loaded)
extra_objects : [string]
list of extra files to link with (eg. object files not implied
by 'sources', static library that must be explicitly specified,
binary resource files, etc.)
extra_compile_args : [string]
any extra platform- and compiler-specific information to use
when compiling the source files in 'sources'. For platforms and
compilers where "command line" makes sense, this is typically a
list of command-line arguments, but for other platforms it could
be anything.
extra_link_args : [string]
any extra platform- and compiler-specific information to use
when linking object files together to create the extension (or
to create a new static Python interpreter). Similar
interpretation as for 'extra_compile_args'.
export_symbols : [string]
list of symbols to be exported from a shared extension. Not
used on all platforms, and not generally necessary for Python
extensions, which typically export exactly one symbol: "init" +
extension_name.
swig_opts : [string]
any extra options to pass to SWIG if a source file has the .i
extension.
depends : [string]
list of files that the extension depends on
language : string
extension language (i.e. "c", "c++", "objc"). Will be detected
from the source extensions if not provided.
optional : boolean
specifies that a build failure in the extension should not abort the
build process, but simply not install the failing extension.
"""
# When adding arguments to this constructor, be sure to update
# setup_keywords in core.py.
def __init__(self, name, sources,
include_dirs=None,
define_macros=None,
undef_macros=None,
library_dirs=None,
libraries=None,
runtime_library_dirs=None,
extra_objects=None,
extra_compile_args=None,
extra_link_args=None,
export_symbols=None,
swig_opts = None,
depends=None,
language=None,
optional=None,
**kw # To catch unknown keywords
):
setup.py
前边介绍过setpu.py文件,它是用来生成外部扩展库动态链接库的,所有的so库都会在这里配置。
它的位置在python库文件夹的顶层目录中,我们需要将它与我们自己编写的外部库联系起来,这样在编译python的时候就可以将其一起编译。
改动:
在文件的开头位置增加我们刚才配置的那些对象
moddirlist
在moddirlist
中增加我们的源文件地址
在exts
对象初始化的时候将我们自己配置的模块放进去
生成so文件
在平台顶级文件夹执行libs编译
查看编译过程
编译成功并拷贝到install文件夹中,可以使用了。
查看执行效果
C扩展库向python传递list tuple dictionary的套路
提供一些官方文档的地址:
https://docs.python.org/3/c-api/intro.html?highlight=pylist_new(英文)
https://www.rddoc.com/doc/Python/3.6.0/zh/c-api/intro/#objects-types-and-reference-counts(中文对照)
tuple
python官方文档为我们提供了一个示例:
PyObject *t;
t = PyTuple_New(3);
PyTuple_SetItem(t, 0, PyLong_FromLong(1L));
PyTuple_SetItem(t, 1, PyLong_FromLong(2L));
PyTuple_SetItem(t, 2, PyUnicode_FromString("three"));
注意:元组类型在C接口中不支持更改,只能被初始化。
初始化tuple接口:PyTuple_New
设置tuple的接口:PyTuple_SetItem
在python中,所有的整型对象都作为长整型来对待
类型转换接口:PyLong_FromLong
*这个接口提供了一个将int型数据转换为(PyObject )类型长整型数据的方法。
此demo提供了一个传递任意长度的int型数组的方法
static PyObject*
extest_array2tuple(PyObject *self, PyObject *args)
{
PyObject *obj_tuple;
int arr[10] ,i;
obj_tuple = PyTuple_New( 10 );
for(i=0; i < 10; i++)
{
arr[i] = i * 10;
if(PyTuple_SetItem(obj_tuple, i, PyLong_FromLong(arr[i])) == -1)
{
perror("PyTuple_SetItem err:");
}
}
return obj_tuple;
}
list
list的接口调用方式和tuple一模一样。
就不赘述了,直接上代码。
static PyObject*
extest_array2list(PyObject *self, PyObject *args)
{
PyObject *obj_list;
int arr[10] ,i;
obj_list = PyList_New( 10 );
for(i=0; i < 10; i++)
{
arr[i] = i * 10;
if(PyList_SetItem(obj_list, i, PyLong_FromLong(arr[i])) == -1)
{
perror("PyList_SetItem err:");
}
}
return obj_list;
}
dict
字典类型的转换demo我设计了一个结构体,在字典里,将结构体的值与变量名对应起来。
struct student{
char name[64];
int age;
float score;
};
static PyObject*
extest_struct2dict(PyObject *self, PyObject *args)
{
struct student s1;
PyObject *obj_dict;
strcpy(s1.name, "xiaoming");
s1.age = 21;
s1.score = 100;
obj_dict = PyDict_New();
PyDict_SetItemString(obj_dict, "name", PyUnicode_FromString(s1.name) );
PyDict_SetItemString(obj_dict, "age", PyLong_FromLong(s1.age) );
PyDict_SetItemString(obj_dict, "score", PyLong_FromLong(s1.score));
return obj_dict;
}
字典类型的初始化接口PyDict_New
可以不提供字典大小,设置元素的接口PyDict_SetItemString
也有些不同。
初始化接口:PyDict_New
新增dict元素接口: PyDict_SetItem
PyDict_SetItemString
执行效果
一种通用方法Py_BuildValue
此方法提供了一个类似于printf的格式控制方式的python对象转换接口
转载自:https://www.rddoc.com/doc/Python/3.6.0/zh/c-api/arg/?highlight=py_buildvalue
PyObject* Py_BuildValue
(const char_ *format_, …)Return value: New reference.
基于类似于 PyArg_Parse*()
系列函数接受的格式字符串和值序列创建新值。返回值或 NULL 在发生错误的情况下;如果返回 NULL,则会引发异常。
格式控制符:s
(str
或 None
)[char *]
使用 'utf-8'
编码将使用空值终止的C字符串转换为Python str
对象。如果C字符串指针是 NULL,则使用 None
。s#
(str
或 None
)[char *,int]
使用 'utf-8'
编码将C字符串及其长度转换为Python str
对象。如果C字符串指针是 NULL,则忽略长度并返回 None
。y
(bytes
)[char *]
这将一个C字符串转换为一个Python bytes
对象。如果C字符串指针是 NULL,则返回 None
。y#
(bytes
)[char *,int]
这将一个C字符串及其长度转换为一个Python对象。如果C字符串指针是 NULL,则返回 None
。z
(str
或 None
)[char *]
与 s
相同。z#
(str
或 None
)[char *,int]
与 s#
相同。u
(str
)[Py_UNICODE *]
将Unicode(UCS-2或UCS-4)数据的以null结尾的缓冲区转换为Python Unicode对象。如果Unicode缓冲区指针是 NULL,则返回 None
。u#
(str
)[Py_UNICODE *,int]
将Unicode(UCS-2或UCS-4)数据缓冲区及其长度转换为Python Unicode对象。如果Unicode缓冲区指针是 NULL,则忽略长度并返回 None
。U
(str
或 None
)[char *]
与 s
相同。U#
(str
或 None
)[char *,int]
与 s#
相同。i
(int
)[int]
将纯C int
转换为Python整数对象。b
(int
)[char]
将纯C char
转换为Python整数对象。h
(int
)[short int]
将纯C short int
转换为Python整数对象。l
(int
)[long int]
将C long int
转换为Python整数对象。B
(int
)[unsigned char]
将C unsigned char
转换为Python整数对象。H
(int
)[无符号短整型]
将C unsigned short int
转换为Python整数对象。I
(int
)[unsigned int]
将C unsigned int
转换为Python整数对象。k
(int
)[无符号长整型]
将C unsigned long
转换为Python整数对象。L
(int
)[长]
将C long long
转换为Python整数对象。K
(int
)[unsigned long long]
将C unsigned long long
转换为Python整数对象。n
(int
)[Py_ssize_t]
将C Py_ssize_t
转换为Python整数。c
(长度为1的 bytes
)[char]
将表示一个字节的C int
转换为长度为1的Python bytes
对象。C
(长度为1的 str
)[int]
将表示一个字符的C int
转换为长度为1的Python str
对象。d
(float
)[双]
将C double
转换为Python浮点数。f
(float
)[float]
将C float
转换为Python浮点数。D
(complex
)[Py_complex *]
将C Py_complex
结构转换为Python复数。O
(object)[PyObject *]
传递一个Python对象(不包括它的引用计数,它增加一个)。如果传入的对象是 NULL 指针,则假定这是由于产生参数的调用发现错误并设置异常引起的。因此,Py_BuildValue()
将返回 NULL,但不会引发异常。如果还没有引发异常,则设置 SystemError
。S
(object)[PyObject *]
与 O
相同。N
(object)[PyObject *]
与 O
相同,除了它不增加对象上的引用计数。当对象通过调用参数列表中的对象构造函数创建时有用。O&
(object)[converter,anything]
通过 converter 函数将 anything 转换为Python对象。该函数使用 anything (应与 void *
兼容)作为其参数调用,并应返回一个“新”Python对象,如果发生错误,则返回 NULL。(items)
(tuple
)[matching-items]
将C值序列转换为具有相同数量项目的Python元组。[items]
(list
)[matching-items]
将C值序列转换为具有相同项目数的Python列表。{items}
(dict
)[matching-items]
将C值序列转换为Python字典。每对连续的C值将一个项目添加到字典,分别用作键和值。
如果格式字符串中有错误,则设置 SystemError
异常并返回 NULL。
示例代码及返回结果展示:
空
Py_BuildValue("")
None
数值
Py_BuildValue("i", 123)
123
元组
Py_BuildValue("iii", 123, 456, 789)
(123, 456, 789)
字符串
Py_BuildValue("s", "hello")
'hello'
元组
Py_BuildValue("ss", "hello", "world")
('hello', 'world')
字符串
Py_BuildValue("s#", "hello", 4)
'hello'
空元组
Py_BuildValue("()")
()
元组
Py_BuildValue("(i)", 123)
(123,)
元组
Py_BuildValue("(ii)", 123, 456)
(123, 456)
元组
Py_BuildValue("(i,i)", 123, 456)
(123, 456)
列表
Py_BuildValue("[i,i]", 123, 456)
[123, 456]
字典类型
Py_BuildValue("{s:i,s:i}", "abc", 123, "def", 456)
{'abc': 123, 'def': 456}
元组的嵌套类型
Py_BuildValue("((ii)(ii))(ii)", 1, 2, 3, 4, 5, 6)
(((1, 2), (3, 4)), (5, 6)
C扩展库编写python接口
接口介绍:
tuple解析函数[PyArg_ParseTuple()](https://www.rddoc.com/doc/Python/3.6.0/zh/c-api/arg/#c.PyArg_ParseTuple)
当我们用C编写一个python接口时,一定会传入一个tuple类型的args,其中包含在接口中传入的必选参数,默认参数(按顺序传入,不能使用"="赋值形式),和可变参数。
我们可以通过[PyArg_ParseTuple()](https://www.rddoc.com/doc/Python/3.6.0/zh/c-api/arg/#c.PyArg_ParseTuple)
将tuple分解成并传入各种C语言形式的数据中。
tuple&keywords解析函数 PyArg_ParseTupleAndKeywords()
在python的C接口中,所有使用 =(例如name=‘Jack’)方式,以及**kw传参的参数都被封装为字典形式,无论这个参数是作为指定默认参数传入的,还是作为关键字参数传入的。
我们可以使用[PyArg_ParseTupleAndKeywords()](https://www.rddoc.com/doc/Python/3.6.0/zh/c-api/arg/#c.PyArg_ParseTupleAndKeywords)
这个接口会解析出包含指定keyword的值,并传入C语言形式的数据中。
点击蓝色链接查看接口详细介绍,接口用法较丰富这里不再赘述,后边会有代码实例。
只带有必选参数的函数
这种函数接口与C语言的大多数接口类型最为相似,应用场景也最多。
/**
* @brief extest_test_plus(位置参数)
* 等于python中: def test_plus(a, b)
* @param self
* @param args
* @return
*/
static PyObject * extest_test_plus(PyObject *self, PyObject *args)
{
int a, b; //参数
// 判断输入的参数是否为整型, 如果不是返回一个NULL值, i 表示整型, &a, &b为输入参数
if(!(PyArg_ParseTuple(args, "ii", &a, &b))){
return NULL;
}
return (PyObject *)Py_BuildValue("i", test_plus(a, b)); // 把C函数中计算的结果转换成Python对象并返回
}
效果:
默认参数的函数
/**
* @brief extest_test_default_para(默认参数)
* 等于python中: def test_default_para(name, age=32, job="worker")
* @param self
* @param args
* @param kwargs
* @return
*/
static PyObject * extest_test_default_para(PyObject *self, PyObject *args, PyObject *kwargs)
{
int a, len, i; //参数
struct dict2struct *d2s;
PyObject *dic_list;
PyObject *dict_item;
char *key;
char *value;
char *name;
//默认参数初始化
int age = 32;
char *job = "worker";
//默认参数列表
static char *kwlist[] = {"name", "age", "job", 0};
// 必填参数:name(字符串类型),可选默认参数:age(数值类型),job(字符串类型)
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|is",
kwlist, &name, &age, &job))//按照顺序将kwlist中的值存放在对应的变量中
return NULL;
printf("name is %s\n", name);
printf("age is %d\n", age);
printf("job is %s\n", job);
return (PyObject *)Py_BuildValue("O", args); // 把C函数中计算的结果转换成Python对象并返回
}
效果:
注:不在kwlist中的参数会导致 TypeError 异常
可变参数的函数(不定长度tuple解析)
这时候就不能用PyArg_ParseTuple这种接口了,因为这个接口是要指定参数数量的。
像这种不同寻常的可变参数,要用一些不同寻常的方法才行。
Py_ssize_t PyTuple_Size
(PyObject_ *p_)
获取指向tuple对象的指针,并返回该元组的大小。
PyObject*** PyTuple_GetItem
(PyObject_ *p_, Py_ssize_t_ pos_)Return value: Borrowed reference.**
返回 p 指向的元组中位置 pos 处的对象。如果 pos 超出范围,返回 NULL 并设置 IndexError
异常。
知道了元组的大小,又有了得到某一个元素的方法(PyObject*** 类型)**就可以遍历元组了
/**
* @brief extest_test_plus_tuple(可变参数)
* 等于python中: def test_plus_tuple(*buf)
* @param self
* @param args
* @return
*/
static PyObject * extest_test_plus_tuple(PyObject *self, PyObject *args)
{
int sum = 0;
int len, pos;
PyObject *tuple_item;
len = PyTuple_Size(args);
for(pos=0; pos<len; pos++)
{
tuple_item = PyTuple_GetItem(args,pos);
//如果是整数类型的对象
if(PyLong_Check(tuple_item))
sum = sum + PyLong_AsLong(tuple_item);
}
return (PyObject *)Py_BuildValue("i", sum); // 把C函数中计算的结果转换成Python对象并返回
}
写了一个将参数中所有数字相加的接口。
效果:
加入一些字符串,看看能不能忽略
传入tuple看看
再玩一些花样看看
关键字参数
明确一下一个python规则:关键字参数的keyword只能为字符串(字典的keyword可以为数字,这是有区别的)
当然如果使用**kw这种形式传进来参数,我们依然可以把它当作字典来解析,也就是说我们自己写的接口中keyword可以为数字。
下面是我写的C接口:
在我们自己的接口中,key实现了整数,小数的传递。
不过为了和python一致,我们还是要做到符合python的语法规则(代码还是保留了,后面可以看到)。
废话不多说,直接上代码
struct dict2struct{
char key[64];
char value[64];
};
static int dict2str(PyObject* dict_item , char *key, char *value)
{
PyObject* py_key = NULL;
PyObject* py_val = NULL;
char *rets;
if(!(PyArg_ParseTuple(dict_item, "OO", &py_key, &py_val)))
{
return 0;
}
if(dict_item == NULL)
{
//printf("the dict is null\n");
return -1;
}
//查看key的类型,并根据类型解析数据,转换为字符串(不符合python规则)
// if(PyUnicode_Check(py_key))
// {
// rets = PyUnicode_AsUTF8(py_key);
// sprintf(key ,"%s",rets);
// //printf("key[%s]\n",key);
// }
// else if (PyLong_Check(py_key))
// {
// long val = PyLong_AsLong(py_key);
// sprintf(key ,"%ld",val);
// //printf("type:int value:%s\n ", value);
// }
// else if (PyFloat_Check(py_key))
// {
// double val = PyFloat_AsDouble(py_key);
// sprintf(key ,"%lf",val);
// // printf("type:double value:%s\n ", value);
// }
//key的类型一定是字符串
if(PyUnicode_Check(py_key))
{
rets = PyUnicode_AsUTF8(py_key);
sprintf(key ,"%s",rets);
//printf("key[%s]\n",key);
}
else
{
printf("TypeError: keywords must be strings\n");
return -1;
}
//查看value的类型,并根据类型解析数据,转换为字符串
if(PyUnicode_Check(py_val))
{
//string
rets = PyUnicode_AsUTF8(py_val);
sprintf(value ,"%s",rets);
//printf("type:string value:%s\n ", value);
}
else if (PyLong_Check(py_val))
{
/* 新版中我没有找到转换为int 的支持,基本上都是long */
long val = PyLong_AsLong(py_val);
sprintf(value ,"%ld",val);
//printf("type:int value:%s\n ", value);
}
else if (PyFloat_Check(py_val))
{
double val = PyFloat_AsDouble(py_val);
sprintf(value ,"%lf",val);
//printf("type:double value:%s\n ", value);
}
else
{
printf("TypeError: invalid syntax\n");
return -1;
}
return 0;
}
int echo_args(char* name, int age, struct dict2struct *d2s, int dict_lenth)
{
int i;
//必选参数
printf("name is %s\n", name);
printf("age is %d\n", age);
//关键字参数
for(i = 0; i < dict_lenth; i++)
{
printf("struct[%d].key = %s\t", i, d2s[i].key);
printf("struct[%d].value = %s\n", i, d2s[i].value);
}
return 0;
}
/**
* @brief extest_test_kw_para(关键字参数)
* 等于python中: def test_kw_para(name, age, **kw)
* @param self
* @param args 参数列表
* @param kw
* @return
*/
static PyObject * extest_test_kw_para(PyObject *self, PyObject *args, PyObject *kwargs)
{
int a, len, i; //参数
struct dict2struct *d2s;
PyObject *dic_list;
PyObject *dict_item;
char *name;
int age = 32;
// 判断输入的参数是否为整型, 如果不是返回一个NULL值, i 表示整型, s为字符串
if(!(PyArg_ParseTuple(args, "si", &name, &age))){
return NULL;
}
//查看字典大小
len = PyDict_Size(kwargs);
printf("PyDict_Size is %d\n", len);
//处理关键字参数
if(len >0)
{
//开辟空间
d2s = (struct dict2struct *)malloc(sizeof(struct dict2struct) * len);
//将字典根据(key, value)的形式转换成列表
dic_list = PyDict_Items(kwargs);
for(i = 0; i < len; i++)
{
//从列表中拿出单个(key, value)元素
dict_item = PyList_GetItem(dic_list, i);
//分析函数
if(dict2str(dict_item , d2s[i].key, d2s[i].value))
{
free(d2s);
return NULL;
}
}
//执行C函数
echo_args(name, age, d2s, len);
//释放空间
free(d2s);
}
return (PyObject *)Py_BuildValue("O", args ); // 必选参数转换成Python对象并返回
}
效果:
当参数不符合规则时:
python回调
获取python接口传入的函数对象
static PyObject *my_callback = NULL;
static PyObject *set_python_cb(PyObject *self, PyObject *args)
{
if (PyArg_ParseTuple(args, "O:set_callback", &my_callback))
{
if (!PyCallable_Check(my_callback))
{
PyErr_SetString(PyExc_TypeError, "parameter must be callable");
return NULL;
}
}
return PyBool_FromLong(TRUE);
}
目前已经获取了python函数对象my_callback,下面介绍如何调用my_callback
C回调函数内使用PyObject_CallObject调用python函数对象
当使用专用的Python API(例如 threading
模块)创建线程时,线程状态会自动与它们相关联,因此上面显示的代码是正确的。但是,从C创建线程时(例如,由具有自己的线程管理功能的第三方库创建),它们不保存GIL,也不为它们提供线程状态结构。
如果您需要从这些线程中调用Python代码(通常这将是上述第三方库提供的回调API的一部分),则必须首先通过创建线程状态数据结构向解释器注册这些线程,然后获取GIL,最后存储它们的线程状态指针,然后才能开始使用Python / C API。完成后,您应该重置线程状态指针,释放GIL,最后释放线程状态数据结构。
该PyGILState_Ensure()
和PyGILState_Release()
功能上面自动完成所有的。从C线程调用Python的典型习惯用法是:
PyEval_InitThreads();
PyGILState_STATE gstate = PyGILState_Ensure();
PyObject *arglist = Py_BuildValue("(s)", "this is a cb test demo");
PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);
PyGILState_Release(gstate);