swig 使用案例

文章展示了如何使用SWIG在C#和Python中处理C++的结构体、数组、指针以及函数指针。在C#中,通过扩展接口实现了结构体数组成员的读写操作,并演示了如何注册和调用回调函数。在Python部分,不仅提供了C风格回调函数注册的方法,还展示了面向对象特性下回调函数的注册。文章强调了在多线程环境和回调函数中的GIL锁管理,并指出在使用SWIG时可能遇到的问题和解决策略。
Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

包含数组、结构体嵌套,指针,函数指针传递等基本操作。swig默认不支持数组元素的写入,如果想操作数组元素,可以附加一些接口函数实现。比如下面在处理结构体的数组成员时,使用%extend命令扩展了对应的操作接口(也可以直接使用swig提供的array_functions功能,此时要用到carrays.i接口定义文件)

*注:使用的swig版本为4.1.1

一. 生成C#的接口
/***** test.h******/
#ifndef _TEST_H_
#define _TEST_H_
#include <stdint.h>
typedef double SEVEN_FIXED_ARRAY[7];
typedef enum _E_TYPE_
{
    E_TP_0 = 0,        
    E_TP_1 = 1,
} E_TYPE;
typedef struct _OPTION_CONFIG_
{
    int          csType; 
    int          csID;    
}OPTION_CONFIG;
typedef struct _ST_PARAM_INFO_
{
    int                  nSp;
    int                  nBr;            
    int                  nTid;                    
    OPTION_CONFIG        cfg;               
                                                    

    int                  nWid;                
    int                  szRes[4];             
}ST_PARAM_INFO;
typedef struct _ST_TARGET_
{
    int                     nId;                   
    E_TYPE                    etype;                
    SEVEN_FIXED_ARRAY        arr;
}ST_TARGET_INFO;
typedef struct _ST_MOV_PARAM_
{
    ST_TARGET_INFO     TargetInfo;
    ST_PARAM_INFO      ParamInfo;
}ST_PARAM_NODE;
typedef struct _ST_PARAM_LIST_
{
    int nNodeNum;
    ST_PARAM_NODE stParam[50];
}ST_PARAM_LIST;

typedef struct _ST_MOTION_SEGMENT_STRU
{
    __int32 iCount;//data中存放的ST_PARAM_NODE数据个数
    __int32 iSegIdx;//轨迹段编号
    ST_PARAM_NODE* data;//指向一片连续内存,存放多个ST_PARAM_NODE数据
}ST_MOTION_SEGMENT;

typedef int (*Fun)(void* msg,int msgLen, void* usrdata);
void Test(ST_PARAM_NODE* param);
void RegisterFunc(Fun usrfunc,void* usrdata);

#endif
/************ test.c ********************/
#include <stdio.h>
#include "test.h"

static Fun dowork = NULL;
static void* workdata = NULL;
void Test(ST_PARAM_NODE* param)
{
    printf( "stMotionParam.nToolId:%d.\r\n ", param->ParamInfo.nTid);
    OPTION_CONFIG cfg = {.csID=1,.csType=2};
    if (dowork != NULL)
        dowork(&cfg,sizeof(OPTION_CONFIG), workdata);
    return;
}
void RegisterFunc(Fun usrfunc, void* usrdata)
{
    dowork = usrfunc;
    workdata = usrdata;
}

/* command: swig.exe -csharp -namespace ModuleTest -outdir ./interfaces example.i*/
/***********************example.i*****************/
%module TEST
%include <windows.i>
%include <carrays.i>
%include <cdata.i>
%include <stdint.i>
%include <cpointer.i>
%{
    #include "test.h"
%}
%array_class(char, CharArray);
%array_class(double, DoubleArray);

%array_functions(ST_PARAM_NODE,ParamNodeArrayOperations);
%array_class(ST_PARAM_NODE,ParamNodeArray);
%pointer_functions(long long int,Int64PtrOp)
%apply void *VOID_INT_PTR {void *}
%include "test.h"
%extend _ST_PARAM_INFO_ {
    int set_szRes(int* res,int count)
    {
        if(count<1||count>4)
            return -1;
        else
        {
            memcpy(self->szRes,res,count*sizeof(int));
            return count;
        }
        
    }
    int get_szRes(int* res,int count)
    {
        if(count<1||count>4)
            return -1;
        else
        {
            memcpy(res,self->szRes,count*sizeof(int));
            return count;
        }
    }
    int getszResByIndex(int idx)
    {
        if(idx<0||idx>3)
            return 0x7FFFFFFF;
        else
            return self->szRes[idx];
    }
};

%extend _ST_PARAM_LIST_ {             // Attach these functions to struct _ST_PARAM_LIST_
  
  int set_stParam(int idx,ST_PARAM_NODE* mp) {
    if(idx<0||idx>self->nNodeNum-1)
     return -1;
    self->stParam[idx] = *mp;
    return 0;
  }
 int get_stParam(int idx,ST_PARAM_NODE* mp) {
    if(idx<0||idx>self->nNodeNum-1)
     return -1;
    *mp = self->stParam[idx];
    return 0;
  }
};

%extend _ST_TARGET_ {
    int set_arr(double* p, int count)
    {
        int posSz = sizeof(self->arr)/sizeof(double);
        if(p==NULL||count<1||count>posSz)
            return -1;
        memcpy(self->arr,p,count*sizeof(double));
        return count;
    }
    int get_arr(double* p, int count)
    {
        int posSz = sizeof(self->arr)/sizeof(double);
        if(p==NULL||count<1||count>posSz)
            return -1;
        memcpy(p,self->arr,count*sizeof(double));
        return count;
    }
};
%inline %{
    int SegmentTest(const void* seg, int sz)
    {
        int res = 0;
        ST_MOTION_SEGMENT* segPtr = (ST_MOTION_SEGMENT*)seg;
        if(sz != sizeof(ST_MOTION_SEGMENT))
            return -1;
        for(int i=0;i<segPtr->iCount;i++)
            res = (i+1) * segPtr->data->TargetInfo.nId + res;
        return res;
    }
    static int HandleInternal=0;
    int GreateHandle(void* handle)//void 二级指针传出
    {
        long long int addr = (long long int)&HandleInternal;
        *(long long int*)handle = addr;
        printf("Handle address: %#x.\r\n",addr);
        return 0;
    }
    void ProcessHandle(void* handle)//void 二级指针传入
    {
        int* handlePtr = *(long long int*)(handle);
        if(handlePtr != NULL)
        {
            printf("Process Handle address: %#x.\r\n",handlePtr);
            *handlePtr += 1;
        }
        printf("HandleInternal Update: %d.\r\n",HandleInternal);
    }
%}



/******************************Program.cs*********************************/
using System;
using System.Runtime.InteropServices;
using ModuleTest;

namespace CsharpClient
{
    class Test
    {
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        public delegate int MyFuncType(IntPtr msgPtr, int msgLen, IntPtr userobj);
        
        public static MyFuncType delegateFunc = new MyFuncType(MyFunc);
        public static int MyFunc(IntPtr msgPtr, int msgLen, IntPtr userObj)
        {
            if (msgPtr == IntPtr.Zero || userObj == IntPtr.Zero|| msgLen < 1)
            {
                Console.WriteLine("Invalid callback data.");
                return -1;
            }
            GCHandle handle = GCHandle.FromIntPtr(userObj);
            Test user = (Test)handle.Target;
            OPTION_CONFIG cfg = new OPTION_CONFIG(msgPtr, false);
            Console.WriteLine("MyFunc Called: {0},obj->x={1},obj->y={2}", cfg.csID, user.id, user.age);
            return 0;
        }
        public int id = 23;
        public int age = 44;
    };
    class Program
    {
        static void Main(string[] args)
        {
            Test user = new Test();
            GCHandle handle = GCHandle.Alloc(user,GCHandleType.Pinned);
            IntPtr handlePtr = handle.AddrOfPinnedObject();
            SWIGTYPE_p_f_p_void_int_p_void__int funcptr = new SWIGTYPE_p_f_p_void_int_p_void__int(Marshal.GetFunctionPointerForDelegate(Test.delegateFunc), false);
            //SWIGTYPE_p_void userPtr = new SWIGTYPE_p_void(handlePtr, false);
            TEST.RegisterFunc(funcptr, handlePtr);

            {
                ST_PARAM_LIST ff = new ST_PARAM_LIST();
                ff.nNodeNum = 9;
                
                int[] szResIn = new int[4];
                int[] szResOut = new int[4];
                szResIn[0] = 1;
                szResIn[1] = 2;
                szResIn[2] = 3;
                szResIn[3] = 4;
                
                IntPtr pszIn = Marshal.UnsafeAddrOfPinnedArrayElement(szResIn, 0);
                IntPtr pszOut = Marshal.UnsafeAddrOfPinnedArrayElement(szResOut, 0);

                ST_PARAM_NODE paraIn = new ST_PARAM_NODE();
                ST_PARAM_NODE paraOut = new ST_PARAM_NODE();
                int i = 0;
                for (; i < ff.nNodeNum; i++)
                {
                    paraIn.TargetInfo.nId = i;
                    SWIGTYPE_p_int szIn = new SWIGTYPE_p_int(pszIn, false);

                    /*************** Set 方法设置*********************/
                    //paraIn.stMotionParam.szRes = szIn;

                    /*************** 自定义接口设置*******************/
                    paraIn.ParamInfo.set_szRes(szIn, 4);
                    ff.set_stParam(i, paraIn);
                }

                i = 0;
                for (; i < ff.nNodeNum; i++)
                {
                    ff.get_stParam(i, paraOut);

                    //SWIGTYPE_p_int szout = paraIn.ParamInfo.szRes;
                    /**************遍历访问*********************/
                    //int j = 0;
                    //for (; j < 4; j++) { 
                    //    IntPtr vptr = SWIGTYPE_p_int.getCPtr(szout).Handle + j;
                    //    szResOut[j] = System.Runtime.InteropServices.Marshal.ReadInt32(vptr);
                    //}

                    /**************批量复制**********************/
                    //Marshal.Copy(SWIGTYPE_p_int.getCPtr(szout).Handle, szResOut, 0,4);

                    /***************接口接收*********************/
                    SWIGTYPE_p_int szout = new SWIGTYPE_p_int(pszOut, false);
                    paraOut.ParamInfo.get_szRes(szout,4);
                    Console.WriteLine("target id={0},sz1={1}", paraOut.TargetInfo.nId, szResOut[1]);
                }
                ST_MOTION_SEGMENT seg = new ST_MOTION_SEGMENT();
                ParamNodeArray nodeArr = new ParamNodeArray(5);
                nodeArr.setitem(0,paraIn);
                nodeArr.setitem(1,paraIn);
                nodeArr.setitem(2,paraIn);
                nodeArr.setitem(3,paraIn);
                nodeArr.setitem(4,paraIn);
                ST_PARAM_NODE nodeRef = new ST_PARAM_NODE(ParamNodeArray.getCPtr(nodeArr).Handle,false);
                seg.data = nodeRef;
                seg.iCount = 5;
                //SWIGTYPE_p_void segPtr = new SWIGTYPE_p_void(ST_MOTION_SEGMENT.getCPtr(seg).Handle, false);
                int res = TEST.SegmentTest(ST_MOTION_SEGMENT.getCPtr(seg).Handle, 24);
                TEST.Test(paraOut);
                SWIGTYPE_p_long_long pint = TEST.new_Int64PtrOp();
                IntPtr pptr = SWIGTYPE_p_long_long.getCPtr(pint).Handle;//二级指针
                TEST.CreadteHandle(pptr);
                TEST.ProcessHandle(pptr);
                TEST.delete_Int64PtrOp(pint);
            }
            if(handle.IsAllocated)
                handle.Free();
        }
    }
}

二.生成python的接口

1. 修改接口文件example.i, 实现C风格的回调函数注册。

/* command: swig.exe -python -outdir ./ example.i*/
%module TEST
%include <windows.i>
%include <stdint.i>
%include <cdata.i>
%include <pybuffer.i>
%{
    #define SWIG_FILE_WITH_INIT
    #include "test.h"
%}

%include "test.h"

%inline%{
    struct CallbackData
    {
        PyObject *PyFunc;
        PyObject *PyData;
        //CallbackData(PyObject* func,PyObject* usrdata)//Construct
        //{
        //    PyFunc = NULL;
        //    PyData = NULL;
        //    if(func != NULL && PyCallable_Check(func))
        //    {
        //        PyFunc = func;
        //        Py_INCREF(func);
        //    }
        //    if(usrdata != NULL)
        //    {
        //        PyData = usrdata;
        //        Py_INCREF(usrdata);
        //    }
        //}
        //~CallbackData()//Deconstruct
        //{
        //    if(PyFunc != NULL)
        //        Py_DECREF(PyFunc);
        //    if(PyData != NULL)
        //        Py_DECREF(PyData);
        //}
    };
%}
%extend CallbackData
{
    void Construct(PyObject* func,PyObject* usrdata)
    {
        self->PyFunc = NULL;
        self->PyData = NULL;
        if(func != NULL && PyCallable_Check(func))
        {
            self->PyFunc = func;
            Py_INCREF(func);
        }
        if(usrdata != NULL)
        {
            self->PyData = usrdata;
            Py_INCREF(usrdata);
        }
    }
    void Deconstruct()
    {
        if(self->PyFunc != NULL)
        {
            Py_DECREF(self->PyFunc);
            self->PyFunc = NULL;
        }
        if(self->PyData != NULL)
        {
            Py_DECREF(self->PyData);
            self->PyData = NULL;
        }
    }
};
%{
    static int PythonCallBack(void* msg, int msgLen,void *usrdata)
    {
       int    dres = 0;
       PyObject *arglist;
       PyObject *result;
       void* udata_void_ptr;
       struct CallbackData* udata_ptr = (struct CallbackData*)0;
       PyGILState_STATE state = PyGILState_UNLOCKED;
       int gilOk = 0;
       if(PyGILState_Check()!=1)
       {
           state = PyGILState_Ensure();
           gilOk = 1;
       }
       int res = SWIG_ConvertPtr((PyObject *)usrdata, &udata_void_ptr,SWIGTYPE_p_CallbackData, 0);
       if (!SWIG_IsOK(res)) {
           SWIG_exception_fail(SWIG_ArgError(res), "userdata registered Wrong");//goto fail when Macro expand.
       }
       udata_ptr = (struct CallbackData *)(udata_void_ptr);
       if (udata_ptr->PyFunc == NULL)
       {
           SWIG_exception_fail(SWIG_ArgError(-1), "regist callback data is not constructed.");
       }
       res = PyCallable_Check(udata_ptr->PyFunc);
       if (!SWIG_IsOK(res))
       {
           SWIG_exception_fail(SWIG_ArgError(res), "callback registered does not have a callable object!");
       }
       
       PyObject* msgdata = SWIG_NewPointerObj(SWIG_as_voidptr(msg), SWIGTYPE_p__OPTION_CONFIG_, 0);
       /*
       static PyObject* msgdata = NULL;//could not be static if PythonCallBack would be called in a thread that is not the same thread that created the instance originally.
       if(msgdata ==NULL)
       {
            msgdata = SWIG_NewPointerObj((OPTION_CONFIG*)memcpy((OPTION_CONFIG*)calloc(1,sizeof(OPTION_CONFIG)),msg,sizeof(OPTION_CONFIG),SWIGTYPE_p__OPTION_CONFIG_,SWIG_POINTER_OWN|0);
       }
       else
       {
           if(SwigPyPacked_Check(msgdata))
           {
               SwigPyPacked* packed_obj = (SwigPyPacked*)msgdata;
               SWIG_ConvertPacked(msgdata,msg,sizeof(OPTION_CONFIG), packed_obj->ty);
           }
       }
       */
       if(msgdata != NULL)
       {
           //arglist = Py_BuildValue("(OiO)",msgdata,msgLen,udata_ptr->PyData);
           //result = PyEval_CallObject(udata_ptr->PyFunc,arglist);
           //Py_DECREF(arglist);
           PyObject *msgLenObj = PyLong_FromLong(msgLen);
           result=PyObject_CallFunctionObjArgs(udata_ptr->PyFunc,msgdata,msgLenObj, udata_ptr->PyData,NULL);
           if (result)
           {
             dres = PyInt_AsLong(result);
           }
           Py_XDECREF(result);
           Py_XDECREF(msgLenObj);
       }
     fail:
       Py_XDECREF(msgdata);//Note: comment this line if msgdata is static
       if(gilOk)
          PyGILState_Release(state);
       return dres;
    }
%}
%inline %{
    void RegisterFunc_wrapper(PyObject *cbData)
    {
        void* vain_ptr;
        int res = SWIG_ConvertPtr(cbData, &vain_ptr,SWIGTYPE_p_CallbackData,0);
        if(SWIG_IsOK(res))
        {   
            struct CallbackData * cbinfo = (struct CallbackData *)(vain_ptr);
            if(cbinfo->PyFunc != NULL && PyCallable_Check(cbinfo->PyFunc))
            {
                RegisterFunc(PythonCallBack, (void *) cbData);
                return;
            }
        }
        SWIG_Error(res,"regist callback data failed.");
    }
%}

%pybuffer_mutable_binary(char *data, size_t size);
%pybuffer_binary(const char* data, size_t size);
%inline %{
    int PyReadBinaryData(char *data, size_t size)
    {
        int ret= snprintf(data, size, "%s", "hello");
        return ret;
    }
    int PySendBinaryData(const char* data, size_t size)
    {
        return size;
    }
%}

2. 修改example.i接口文件,利用features 面向对象特性,实现回调函数的注册。

/* command: swig.exe -c++ -python -outdir ./ example.i*/
%module(directors="1") TEST
%include <windows.i>
%include <stdint.i>
%include <carrays.i>
%include <cdata.i>
%{
    #define SWIG_FILE_WITH_INIT
    #ifdef __cplusplus
    extern "C"  {  
    #endif
        #include "test.h"
    #ifdef __cplusplus
    }
    #endif
%}
 
#ifdef __cplusplus
extern "C"  {    
#endif
    %include "test.h"
#ifdef __cplusplus
}   
#endif
%array_functions(int,intArray);
%array_functions(ST_PARAM_NODE,paramNodeArray);
%feature("director") BinaryOp;
%inline %{
struct BinaryOp {
  virtual int handle(OPTION_CONFIG* msg, int msgLen, void* data) = 0;
  virtual ~BinaryOp() {}
};
%}
%{
    static int CallbackInternal(void* msg, int msgLen, void* usrdata) 
    {
        if(usrdata != NULL)
        {
            BinaryOp* handler_ptr = (BinaryOp*)usrdata
            return handler_ptr->handle((OPTION_CONFIG*)msg, msgLen, usrdata);
        }
        else
            return 0;
    }
    
%}
%inline %{
    void RegisterFunc_wrapper(BinaryOp *handler, void* usrdata) {
        RegisterFunc(&CallbackInternal,(void*)handler);
        handler = NULL;
        return;
    }
%}
 

3. 编写python打包脚本。如果使用1中的接口定义,swig生成的接口文件example_wrap.c为C语言风格;如果使用2中的接口定义,则生成的example_wrap.cxx为c++风格。因此在写python打包脚本时,Extension的sources中要根据实际情况选择example_wrap.c或者example_wrap.cxx。

#!/usr/bin/env python

"""
setup.py file for SWIG example
"""

from distutils.core import setup, Extension

"""
swig生成的example_wrap.c 中有一个SWIG_init宏定义,根据python版本不同,会有所区别,
即PyInit_xxxx。 python3以上版本,强制以下划线开头。因此,这里的Extension扩展模块
的名字也要设为:下划线+模块名。
如果test.c还用到了第三方库,则可以在library_dirs和libraries中进行配置
"""
example_module = Extension('_TEST',
                           sources=['example_wrap.cxx', 'test.c'],
                           include_dirs=[".",],
                           library_dirs=[".",],
                           libraries=[],
                           )
"""
py_modules 指定要打包的模块,指定为上面的Extension扩展模块。
"""
setup (name = 'my_test_package',
       version = '0.1',
       author      = "SWIG Docs",
       description = """Simple swig example from docs""",
       ext_modules = [example_module],
       py_modules = ["_TEST"],
       )

4. 执行python打包命令, 得到python可用的包(生成的_TEST.pyd文件)

python setup.py build_ext --inplace

5. 可以把_TEST.pyd文件和swig生成的TEST.py文件拷贝到python项目中,进行测试。如果3中用的是example_wrap.cxx,则需要继承实现虚函数,重写回调函数实现。如果用的是example_wrap.c则不需要做额外的操作。

import ctypes
import struct
import TEST


class PythonBinaryOp(TEST.BinaryOp):

    # Define Python class 'constructor'
    def __init__(self):
        # Call C++ base class constructor
        TEST.BinaryOp.__init__(self)

    # Override C++ method: virtual int handle(OPTION_CONFIG* msg, int msgLen, void* data) = 0;
    def handle(self, msg, msgLen, usrdata):
        # print(help(msg))
        print("msg: ",msg.thisown, msg.csID, msg.csType)
        print("msgLen: ",msgLen)
        print("usrdata: ", usrdata)
        return 0


if __name__ == '__main__':
    # print(help(TEST))
    ss = "hello world"
    c_s = ctypes.c_wchar_p(ss)
    c_void_p_ss = ctypes.cast(c_s, ctypes.c_void_p)
    handler = PythonBinaryOp()
    TEST.RegisterFunc_wrapper(handler, None)  # can not pass c_void_p_ss here
    node = TEST.ST_PARAM_NODE()
    TEST.intArray_setitem(node.ParamInfo.szRes, 0, 5)
    print(TEST.intArray_getitem(node.ParamInfo.szRes, 0))
    binary_szRes = TEST.cdata(node.ParamInfo.szRes, 4*4).encode()
    print("binary_szRes len: ",binary_szRes.__len__())
    print("binary_szRes data: ",binary_szRes)
    print(struct.unpack("<iiii",binary_szRes))
    TEST.Test(node)
import TEST


def xxxx(msg, msgLen, pyobj):
    print("msg: ",type(msg),msg.thisown,msg.csID)
    print("msgLen: ",msgLen)
    print("usrdata: ", pyobj)
    return 0


if __name__ == '__main__':
    #print(help(TEST))
    buffer = bytearray(10)
    TEST.PyReadBinaryData(buffer)
    TEST.PySendBinaryData(b"hello world")
    ss = "hello world"
    cbdata = TEST.CallbackData()
    cbdata.Construct(xxxx,ss)
    TEST.RegisterFunc_wrapper(cbdata)
    #cbdata.Deconstruct()
    node = TEST.ST_PARAM_NODE()
    TEST.Test(node)

    6. 关于python的回调:由于python有一个全局解释锁GIL,在执行python代码时,必须先要拿到这个锁。这样一来,python向c/c++模块中注册的函数,在c/c++发起调用时也受到该锁的约束。如果c/c++底层有其他非python线程异步执行python任务,在没有显式地拿锁的情况下,这些线程直接调用python的回调接口将引起GIL锁竞争异常,造成python内部对象引用计数出现混乱,出现"gc object already tracked",引发GC崩溃。因此在多线程环境中调用python的回调函数,需要提前拿锁:PyGILState_Ensure()/PyGILState_Release(),如上面第一小节中接口封装示例(注意,必须在创建任何PyObject对象之前就执行拿锁操作,否则这些对象的引用计数会有问题)。

三. 有选择地导出接口或数据结构

很多时候,我们并不想把c++所有的数据结构或者接口都导出来,暴露过多细节,这种情况下应该对c/c++接口头文件进行拆分(阅读文档相关章节了解详细内容:SWIG Basics)。

四. 没有银弹

起初接触到swig时,发现它竟然支持那么多种语言的转换,简直惊为天人。确实很强大,但是在使用过程中发现还是存在一些问题。往往c/c++底层模块的提供方只提供c/c++风格的接口,而使用方想要用的时候,如果不熟悉c/c++的话,接口.i文件中也很难去实现数据类型转换,生成的接口基本都是要求指针操作。为了在c#或python这样的语言中调用这些接口,则必须做一系列操作,将原生C#或python数据对象的指针取出来传给接口。虽然swig本意是要求使用方直接使用接口提供的数据类型,而非原生数据,但程序员应该更习惯用原生数据,所以更倾向于做这些转换动作。其中函数指针是一个特殊的数据类型,这种转换可能更加复杂。比如要在python代码中向swig接口注册一个c风格的回调函数,需要编写CPython代码在底层实现数据转换,这实在太费劲了。如果可以,尽量利用C++面向对象的多态性来实现,直接继成swig生成的接口类,并重写虚方法。对于C类型的库,最好用C++封装一层再用swig生成接口(类似上面在接口文件中定义了struct BinaryOp,然后在python中继承并实现重载)。但是,这样感觉已经背离了使用swig的初衷,直接利用脚本语言自带的C/C++扩展接口可能更方便。

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值