C扩展python接口\C回调调用python接口

C语言编写CPython扩展库

很多时候C语言的效率要比Python高很多,所以通过C编写python扩展库在处理能力不太强劲的芯片上将有较好的效果。

扩展库的编写

下面通过一个小Demo工程描述扩展库是怎么配置编译的,在分享编写过程前先介绍一下工程的目录结构

工程目录结构

image.png


在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的时候就可以将其一起编译。
改动:
image.png
在文件的开头位置增加我们刚才配置的那些对象

image.pngmoddirlist
moddirlist中增加我们的源文件地址


image.png
exts对象初始化的时候将我们自己配置的模块放进去

生成so文件

在平台顶级文件夹执行libs编译image.png
查看编译过程
image.png
编译成功并拷贝到install文件夹中,可以使用了。

查看执行效果

image.png

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
image.png


设置tuple的接口:PyTuple_SetItem
image.png




在python中,所有的整型对象都作为长整型来对待
image.png
类型转换接口:PyLong_FromLong
*这个接口提供了一个将int型数据转换为(PyObject )类型长整型数据的方法
image.png


此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
image.png


新增dict元素接口: PyDict_SetItem PyDict_SetItemString
image.png

执行效果

image.png

一种通用方法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,则会引发异常。
格式控制符:
sstrNone)[char *]
使用 'utf-8' 编码将使用空值终止的C字符串转换为Python str 对象。如果C字符串指针是 NULL,则使用 None
s#strNone)[char *,int]
使用 'utf-8' 编码将C字符串及其长度转换为Python str 对象。如果C字符串指针是 NULL,则忽略长度并返回 None
ybytes)[char *]
这将一个C字符串转换为一个Python bytes 对象。如果C字符串指针是 NULL,则返回 None
y#bytes)[char *,int]
这将一个C字符串及其长度转换为一个Python对象。如果C字符串指针是 NULL,则返回 None
zstrNone)[char *]
s 相同。
z#strNone)[char *,int]
s# 相同。
ustr)[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
UstrNone)[char *]
s 相同。
U#strNone)[char *,int]
s# 相同。
iint)[int]
将纯C int 转换为Python整数对象。
bint)[char]
将纯C char 转换为Python整数对象。
hint)[short int]
将纯C short int 转换为Python整数对象。
lint)[long int]
将C long int 转换为Python整数对象。
Bint)[unsigned char]
将C unsigned char 转换为Python整数对象。
Hint)[无符号短整型]
将C unsigned short int 转换为Python整数对象。
Iint)[unsigned int]
将C unsigned int 转换为Python整数对象。
kint)[无符号长整型]
将C unsigned long 转换为Python整数对象。
Lint)[长]
将C long long 转换为Python整数对象。
Kint)[unsigned long long]
将C unsigned long long 转换为Python整数对象。
nint)[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 对象。
dfloat)[双]
将C double 转换为Python浮点数。
ffloat)[float]
将C float 转换为Python浮点数。
Dcomplex)[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)[converteranything]
通过 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对象并返回
}

效果:

image.png

默认参数的函数


/**
 * @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对象并返回
}

效果:
image.png
注:不在kwlist中的参数会导致 TypeError 异常
image.png

可变参数的函数(不定长度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对象并返回
}

写了一个将参数中所有数字相加的接口。
效果:
image.png
加入一些字符串,看看能不能忽略
image.png
传入tuple看看
image.png
再玩一些花样看看
image.png
image.png

关键字参数

明确一下一个python规则:关键字参数的keyword只能为字符串(字典的keyword可以为数字,这是有区别的)
image.png
当然如果使用**kw这种形式传进来参数,我们依然可以把它当作字典来解析,也就是说我们自己写的接口中keyword可以为数字。
下面是我写的C接口:
image.png
在我们自己的接口中,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对象并返回
}

效果:
image.png
当参数不符合规则时:
image.png

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);

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值