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

1万+

被折叠的 条评论
为什么被折叠?



