以前一直以为C不支持面向对象,为python写扩展真是太憋屈了,但是今天看了SWIG的文档,原来人家早就解决了这个问题。

由于之前看的是《Python科学计算》上面关于swig的简单介绍,上面说了如何在python中如何像类一样的使用C结构,但是也只是停留在如何使用结构的成员数据上,但是没有讲如何将C的函数封装成python的类方法的方法。相反由于C++有原生OOP的支持,固然swig直接可以将C++的类封装成python的类。所以我以前一直认为这时候只能用C++了。
但其实,还是读书读的少啊。swig这么强大的封装工具,这早就想到了。毕竟OO是一种思想,C照样能写出OO的代码来(多重继承和多态这种行么?)。

所以今天看到swig文档中关于%extend指令的介绍才感觉相见恨晚啊。

给C结构添加成员函数

C语言的OO通常都是写操作结构的接口函数,尽管SWIG为C++提供了直接的面向对象的类映射,C却没有。尽管如此SWIG还是提供了一个%extend指令,就是这个指令能够将C语言封装成支持OO的语言的接口。


向已定义的结构添加成员函数

具体的使用方法:

1
2
3
typedef struct Vector {
double x, y, z;
} Vector;

这样封装的话在python中只能这样调用:

1
2
3
4
5
>>> v = Vector(1.0, 2.0, 3.0);
>>> v.x
>>> 1.0

但是这个类并没有成员函数,那如何给这个结构绑定函数,并封装成python类的成员函数呢?上%extend!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// file : vector.i
%module modulename
%{
#include "vector.h" // 这一部分会原封不动的复制到生成封装c源码中,是给C编译器看的
%}
%include "vector.h" // 在这个.i文件中宏展开,让后面的函数知道结构定义
%extend Vector { // 将里面的函数绑定到Vector结构,如果没有typedef的话要写成struct Vector
// 类似C++的构造函数,但是要进行动态内存分配
Vector(double x, double y, double z)
{
Vector * v;
v = (Vector *)malloc(sizeof(Vector));
v->x = x;
v->y = y;
v->z = z;
}
// 析构函数,将构造函数动态分配的内存释放掉。
~Vector()
{
free($self);
}
void print()
{
printf("Vector [%g, %g, %g]\n", $self->x, $self.y, $self->z);
}
};

是的,通过上面的代码可以看出SWIG在模仿C++的语法,其中构造函数和析构函数就不多说了。$self是一个内置的变量,作用类似于C++中的this,也就是指向当前对象的指针。

%extend指令不仅定义了成员函数,而且还相当于在vector.i文件中对这些函数进行了声明,这样不仅C编译器会编译这些函数,SWIG也会处理这些函数生成接口代码。

这样我们在Python中就可以这样使用:

1
2
3
4
5
6
7
8
>>> from vector import *
>>> v = Vector(1, 2, 3)
>>> v.print()
[1, 2, 3]
>>> del v # 删除此对象的引用


在定义结构的时候直接添加成员函数

1
2
3
4
5
6
7
8
9
10
11
12
13
// file : vector.i
%module modulename
%{
#include "vector.h"
%}
typedef struct Vector {
double x, y, z;
%extend {
Vector(double x, double y, double z) { ... }
~Vector() { ... }
...
}
} Vector;


使用已定义的函数作为结构的成员函数

如果在源代码中的实现文件中已经定义结构的接口函数(名称要按照swig的规定),则可以直接在结构定义中扩展这些函数。
例如如果我在实现文件中定义了如下几个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// file : vector.c
#include "vector.h"
Vector * new_Vector(double x, double y, double z)
{
Vector * v;
v = (Vector *)malloc(sizeof(Vector));
v->x = x;
v->y = y;
v->z = z;
return v;
}
void delete_Vector(Vector * v)
{
free(v);
}
void Vector_print(Vector * v)
{
printf("Vector [%g, %g, %g]\n", $self->x, $self.y, $self->z);
}

然后我在*.i文件中给SWIG声明Vector类的时候就可以直接添加成员函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// file : vector.i
%module modulename
%{
#include "vector.h"
%}
typedef struct Vector {
double x, y, z;
%extend {
Vector(int, int, int); // 这个函数将会调用实现文件中的new_Vector()
~Vector(); // 将会调用delete_Vector()
void print(); // 将调用Vector_print()
}
};

这里感觉和C++很像了,有了`%extend`的struct就相当于直接在头文件中定义类(包括成员数据和成员函数),然后类成员函数的实现在实现代码中。

但是这里的命名是由要求的,因为在python中或者其他目标语言中调用成员方法是通过调用Vector_print来实现的,因此如果要认为定义成员函数,函数的名称也要符合这些规则。

Comments