You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Cython扩展类型封装EPANET C项目API?

EPANET C项目的Cython封装类实现问题与解决

问题背景

我正在给EPANET这个C项目编写Cython封装,已经成功实现了单个函数(如en_epanet())的封装,但希望将更多API整合到单个类中,计划通过Cython扩展类型来实现。

现有可运行的单函数封装代码

cdef extern from "types.h":
    cdef struct Project:  # C项目中的结构体
        pass

ctypedef Project *EN_Project

cdef extern from "epanet2_2.h":
    int EN_createproject(EN_Project *)
    int EN_runproject(EN_Project, const char *, const char *, const char *)
    int EN_deleteproject(EN_Project)
    int EN_open(EN_Project, const char *, const char *, const char *)


def en_epanet(char *f1, char *f2, char *f3):
    cdef:
        EN_Project p
        int errcode
    EN_createproject(&p)
    errcode = EN_runproject(p, f1, f2, f3)
    EN_deleteproject(p)
    return errcode

尝试过程中的错误与修正

第一次尝试错误

编写的类代码:

cdef class Epanet22:
    cdef:
        EN_Project project
        int errcode
    def __cinit__(self):
        self.p = <EN_Project>malloc(sizeof(Project))
        if not self.p:
            raise MemoryError("Could not allocate memory for EN_Project")
        EN_createproject(self.p)

编译错误:

EN_createproject(self.p)
^
src\cyepanet.pyx:100:29: Cannot convert Python object to 'EN_Project *'

错误原因:变量名不匹配(类中定义的是project,代码里误用了self.p),且错误手动分配内存,同时EN_createproject需要EN_Project*类型参数,传入的self.p是EN_Project类型,类型不匹配。

第二次尝试错误

修改后的代码:

cdef class Epanet22: 
    cdef: 
        EN_Project project 
        int errcode 
    def __cinit__(self): 
        self.project = <EN_Project>malloc(sizeof(Project)) 
        if not self.project: 
            raise MemoryError("Could not allocate memory for EN_Project") 
        EN_createproject(self.project) 

编译错误:

src\cyepanet.pyx:100:29: Cannot assign type 'EN_Project' to 'EN_Project *'

错误原因:EN_createproject要求参数为EN_Project*(即Project**),但传入的self.project是EN_Project(即Project*),类型不匹配;且手动malloc完全多余,EN_createproject内部会负责分配Project结构体的内存。

第三次尝试错误

再次修改的代码:

cdef class Epanet22:
    cdef:
        EN_Project *project
        int errcode
    def __cinit__(self):
        self.project = <EN_Project*>malloc(sizeof(Project))
        if not self.project:
            raise MemoryError("Could not allocate memory for EN_Project")
        EN_createproject(self.project)
    def __dealloc__(self):
        EN_deleteproject(self.project)
        free(self.project)

编译错误:

EN_deleteproject(self.project) src\cyepanet.pyx:162:29: Cannot assign type 'EN_Project *' to 'EN_Project'

错误原因:EN_deleteproject要求参数为EN_Project(即Project*),但传入的self.project是EN_Project*(即Project**),类型不匹配;且手动malloc和free都是错误操作,EPANET的API已经封装了完整的内存管理逻辑。

正确的Cython扩展类型实现

核心是遵循EPANET API的内存管理规则:EN_createproject自动分配Project结构体内存,EN_deleteproject自动释放该内存,无需手动调用malloc或free。

cdef extern from "types.h":
    cdef struct Project:
        pass

ctypedef Project *EN_Project

cdef extern from "epanet2_2.h":
    int EN_createproject(EN_Project *)
    int EN_runproject(EN_Project, const char *, const char *, const char *)
    int EN_deleteproject(EN_Project)
    int EN_open(EN_Project, const char *, const char *, const char *)


cdef class Epanet22:
    cdef:
        EN_Project project  # EN_Project本身就是指针类型,无需额外加*
        int errcode

    def __cinit__(self):
        # 调用EN_createproject分配内存,传入&self.project让函数填充指针
        self.errcode = EN_createproject(&self.project)
        if self.errcode != 0:
            raise RuntimeError(f"创建项目失败,错误码: {self.errcode}")

    def __dealloc__(self):
        # 确保project指针非空时再释放
        if self.project is not NULL:
            EN_deleteproject(self.project)
            # 无需手动free,EN_deleteproject会处理内存释放

    def run_project(self, char *inp_file, char *rpt_file, char *out_file):
        """运行EPANET项目"""
        self.errcode = EN_runproject(self.project, inp_file, rpt_file, out_file)
        return self.errcode

    def open_project(self, char *inp_file, char *rpt_file, char *out_file):
        """打开EPANET项目"""
        self.errcode = EN_open(self.project, inp_file, rpt_file, out_file)
        return self.errcode

关键说明

  1. 类型匹配:EN_Project是Project*的typedef,类成员定义为EN_Project project即可,无需额外声明为指针的指针。
  2. 内存管理:完全依赖EPANET的API进行内存分配和释放,不要手动调用malloc或free,避免内存泄漏或双重释放。
  3. 方法扩展:可以按照这个模式继续封装更多EPANET API方法,直接在类中添加新的成员函数即可。

内容的提问来源于stack exchange,提问作者Mark Wilson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 16:25:55