在Cython中使用来自不同库的结构体

3

我正在尝试在Cython中调用一个C函数,头文件如下:

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <ctype.h>
#include <unistd.h>
#include <math.h>

#include <apriltag.h>
#include <tag36h11.h>

#include <common/getopt.h>
#include <common/image_u8.h>
#include <common/image_u8x4.h>
#include <common/pjpeg.h>
#include <common/zarray.h>

apriltag_detection_t* scan_frame(int width, int height, uint8_t* data);

如你所见,我想返回一个结构体数组,其中类型定义为apriltag_detection_t。根据文档,为了能在Cython中使用它,我必须定义一种类似于头文件的pxd文件。
然而,apriltag_detection_t是已经在apriltag.h中定义的类型。此外,apriltag_detection_t的成员已经在apriltag.h中定义过了。在使用这个库之前,我需要递归地手动重新定义所有这些类型吗?我应该在哪里编写它们呢?
谢谢!
更新6:
最终到了封装函数的步骤!
from libc.stdint cimport uint8_t

cdef extern from "<apriltag.h>":
    cdef struct apriltag_detection:
        int id
        double c[2]
        double p[4][2]

    ctypedef apriltag_detection apriltag_detection_t

cdef extern from "tag36h11_detector/tag36h11_detector.h":
    apriltag_detection_t* scan_frame(int width, int height, uint8_t* data);

def detect(width, height, frame):
    return scan_frame(width, height, frame)

tag36h11_detector.pyx:15:21: 无法将'apriltag_detection_t *'转换为Python对象

apriltag_detection_t*是一个结构体数组

更新5 这似乎已经解决了。

from libc.stdint cimport uint8_t

cdef extern from "<apriltag.h>":
    cdef struct apriltag_detection:
        int id
        double c[2]
        double p[4][2]

    ctypedef apriltag_detection apriltag_detection_t

cdef extern from "tag36h11_detector/tag36h11_detector.h":
    apriltag_detection_t* scan_frame(int width, int height, uint8_t* data);

更新 4 通过导入必要的类型解决了之前的问题。

from libc.stdint cimport uint8_t

cdef extern from "apriltag.h":
    cdef struct apriltag_detection:
        int id
        double c[2]
        double p[4][2]

    ctypedef apriltag_detection apriltag_detection_t

cdef extern from "tag36h11_detector.h":
    apriltag_detection_t* scan_frame(int width, int height, uint8_t* data);

tag36h11_detector.c:533:10: 致命错误:找不到'apriltag.h'文件

我不确定这是从哪里来的,因为我的头文件,正如原始帖子中提供的那样,需要<apriltag.h>而不是"apriltag.h"。这是我的setup.py的样子。

from distutils.core import setup, Extension
from Cython.Build import cythonize

setup(ext_modules=cythonize(Extension(\
    name='tag36h11_detector', \
    sources=["tag36h11_detector.pyx", \
    "tag36h11_detector/tag36h11_detector.c"], \
    include_path=["/usr/local/include/apriltag"], \
    libraries=["apriltag"])))

UPDATE 3

cdef extern from "apriltag.h":
    cdef struct apriltag_detection:
        int id
        double c[2]
        double p[4][2]

    ctypedef apriltag_detection apriltag_detection_t

cdef extern from "tag36h11_detector.h":
    apriltag_detection_t* scan_frame(int width, int height, uint8_t* data);

tag36h11_detector.pyx:10:60: 'uint8_t'不是类型标识符

更新2

以下是我的当前代码和编译错误

// tag36h11_detector.pyx
cdef extern from "apriltag.h":
    ctypedef apriltag_detection_t:
        int id
        double c[2]
        double p[4][2]

cdef extern from "tag36h11_detector.h":
    apriltag_detection_t* scan_frame(int width, int height, uint8_t* data);

// apriltag.h
    ...
    typedef struct apriltag_detector apriltag_detector_t;
    ...

tag36h11_detector.pyx:2:33: 在ctypedef语句中出现语法错误

更新1

所以我正在尝试使用在apriltag.h(来自库)中定义的类型与上述头文件进行Python接口(我编写并实现了它)。

cdef extern from "apriltag.h":
    struct apriltag_detection:
        int id
        double c[2]
        double p[4][2]

cdef extern from "tag36h11_detector.h":
    struct apriltag_detection* scan_frame(int width, int height, uint8_t* data);

尝试编译上述代码时,我遇到了以下错误:

针对您更新的问题,您是否尝试在您的pxd文件中将结构体定义为ctypedef struct apriltag_detection - undefined
有趣。通常,我会在一个单独的pxd文件中编写我的c库包装器。在编写它们时,我发现一些在线示例非常有帮助。这些示例分别是OpenGLSDL2,它们给了我一些帮助。而这里是一个我正在编写中的不完整的chipmunk物理库的示例。希望这些示例能帮助你找到一种不同的方法! - undefined
@ead 在原始头文件 apriltag.h 中,struct 实际上是 typedef struct apriltag_detection apriltag_detection_t,所以我尝试考虑了这一点,但是我得到了一个不同的错误。 - undefined
你真快!如果头文件的路径相对于pyx文件(例如在同一个文件夹中),则应使用"",如果是从包含路径中引用,则使用"<>"。 - undefined
谢谢你的帮助!我觉得我离成功很近了.. 我遇到了一个错误:"无法将 apriltag_detection_t* 转换为 Python 对象"。这是一个结构体数组,所以我不确定如何编写合适的 Cython 代码。 - undefined
显示剩余4条评论
1个回答

6
这基本上在Cython文档的这一部分中有所涵盖。文档指出,您只需要导入在Cython代码中使用到的部分即可。
例如,让我们看下面的C接口:
#struct.h
struct Needed{
  int a;
};

struct NotNeeded{
  int b;
};

struct Combined{
  struct Needed needed;
  struct NotNeeded notneeded;
};

struct Combined create(void);

您希望调用函数 create 并使用 Needed 结构中的值 a ,这意味着您需要在cython代码中导入 struct Needed struct Combined 的部分,但不需要 NotNeeded

#struct_import.pyx
cdef extern from "struct.h":
     struct Needed:   # use "ctypedef struct Needed" if defined with typedef in h-file! 
            int a  
     struct Combined:  #NotNeeded is missing!
          Needed needed 
     Combined create()  

def get_needed():
    return create().needed.a #should be 42!

现在,使用setup.py(其内容可以在下面看到,struct.c的内容也是如此),我们得到了预期的结果。
[] python setup.py build_ext --inplace
[] python -c "python -c "import struct_import as si; print si.get_needed()"
    42 

如果您使用Cython将一些C代码粘合在一起,可能甚至不需要太多的工作。在我们的示例中,如果我们有一个C函数可以从Combined结构中提取所需的值:

#struct.h 
...
int extract(struct Combined combined);//returns combined.needed.a

我们可以在pyx文件中按如下方式使用它:
#struct_import.pyx
cdef extern from "struct.h":
     struct Combined: 
          pass  #nothing imported!
     Combined create()  
     int extract(Combined combined)

def get_needed():
    return extract(create()) #should be 42!

尽管我们根本没有导入Needed结构体,但它与第一个版本一样好用。

因此,如果导入所有这些结构体变成了一件繁琐的事情,那么可以扩展C接口以使其不必要。


为了使示例完整,这里是缺失的setup.py和struct.c文件:

#setup.py:
from distutils.core import setup, Extension
from Cython.Build import cythonize

setup(ext_modules=cythonize(Extension(
            name='struct_import',
            sources = ["struct_import.pyx", "struct.c"]
    )))

并且

//struct.c
#include "struct.h"

struct Combined create(){
  struct Combined res;
  res.needed.a=42;
  res.notneeded.b=21;
  return res;
}

int extract(struct Combined combined){
   return combined.needed.a;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接