# 15.9 用WSIG包装C代码

## 问题

你想让你写的 C 代码作为一个 C 扩展模块来访问，想通过使用 [Swig包装生成器](http://www.swig.org/) 来完成。

## 解决方案

Swig 通过解析 C 头文件并自动创建扩展代码来操作。 要使用它，你先要有一个 C 头文件。例如，我们示例的头文件如下：

```cpp
/* sample.h */

#include <math.h>
extern int gcd(int, int);
extern int in_mandel(double x0, double y0, int n);
extern int divide(int a, int b, int *remainder);
extern double avg(double *a, int n);

typedef struct Point {
    double x,y;
} Point;

extern double distance(Point *p1, Point *p2);
```

一旦你有了这个头文件，下一步就是编写一个 Swig ”接口”文件。 按照约定，这些文件以 ”.i” 后缀并且类似下面这样：

```cpp
// sample.i - Swig interface
%module sample
%{
#include "sample.h"
%}

/* Customizations */
%extend Point {
    /* Constructor for Point objects */
    Point(double x, double y) {
        Point *p = (Point *) malloc(sizeof(Point));
        p->x = x;
        p->y = y;
        return p;
   };
};

/* Map int *remainder as an output argument */
%include typemaps.i
%apply int *OUTPUT { int * remainder };

/* Map the argument pattern (double *a, int n) to arrays */
%typemap(in) (double *a, int n)(Py_buffer view) {
  view.obj = NULL;
  if (PyObject_GetBuffer($input, &view, PyBUF_ANY_CONTIGUOUS | PyBUF_FORMAT) == -1) {
    SWIG_fail;
  }
  if (strcmp(view.format,"d") != 0) {
    PyErr_SetString(PyExc_TypeError, "Expected an array of doubles");
    SWIG_fail;
  }
  $1 = (double *) view.buf;
  $2 = view.len / sizeof(double);
}

%typemap(freearg) (double *a, int n) {
  if (view$argnum.obj) {
    PyBuffer_Release(&view$argnum);
  }
}

/* C declarations to be included in the extension module */

extern int gcd(int, int);
extern int in_mandel(double x0, double y0, int n);
extern int divide(int a, int b, int *remainder);
extern double avg(double *a, int n);

typedef struct Point {
    double x,y;
} Point;

extern double distance(Point *p1, Point *p2);
```

一旦你写好了接口文件，就可以在命令行工具中调用 Swig 了：

```
bash % swig -python -py3 sample.i
```

swig 的输出就是两个文件，`sample_wrap.c` 和 `sample.py`。 后面的文件就是用户需要导入的。 而 `sample_wrap.c` 文件是需要被编译到名叫 `_sample` 的支持模块的 C 代码。 这个可以通过跟普通扩展模块一样的技术来完成。 例如，你创建了一个如下所示的 `setup.py` 文件：

```python
# setup.py
from distutils.core import setup, Extension

setup(name='sample',
      py_modules=['sample.py'],
      ext_modules=[
        Extension('_sample',
                  ['sample_wrap.c'],
                  include_dirs = [],
                  define_macros = [],

                  undef_macros = [],
                  library_dirs = [],
                  libraries = ['sample']
                  )
        ]
)
```

要编译和测试，在 setup.py 上执行 python3，如下：

```
bash % python3 setup.py build_ext --inplace
running build_ext
building '_sample' extension
gcc -fno-strict-aliasing -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes
-I/usr/local/include/python3.3m -c sample_wrap.c
 -o build/temp.macosx-10.6-x86_64-3.3/sample_wrap.o
sample_wrap.c: In function ‘SWIG_InitializeModule’:
sample_wrap.c:3589: warning: statement with no effect
gcc -bundle -undefined dynamic_lookup build/temp.macosx-10.6-x86_64-3.3/sample.o
 build/temp.macosx-10.6-x86_64-3.3/sample_wrap.o -o _sample.so -lsample
```

如果一切正常的话，你会发现你就可以很方便的使用生成的 C 扩展模块了。例如：

```python
>>> import sample
>>> sample.gcd(42,8)
2
>>> sample.divide(42,8)
[5, 2]
>>> p1 = sample.Point(2,3)
>>> p2 = sample.Point(4,5)
>>> sample.distance(p1,p2)
2.8284271247461903
>>> p1.x
2.0
>>> p1.y
3.0
>>> import array
>>> a = array.array('d',[1,2,3])
>>> sample.avg(a)
2.0
```

## 讨论

Swig 是 Python 历史中构建扩展模块的最古老的工具之一。 Swig 能自动化很多包装生成器的处理。

所有 Swig 接口都以类似下面这样的为开头：

```cpp
%module sample
%{
#include "sample.h"
%}
```

这个仅仅只是声明了扩展模块的名称并指定了 C 头文件， 为了能让编译通过必须要包含这些头文件（位于 `%{` 和 `%}` 的代码）， 将它们之间复制粘贴到输出代码中，这也是你要放置所有包含文件和其他编译需要的定义的地方。

Swig 接口的底下部分是一个 C 声明列表，你需要在扩展中包含它。 这通常从头文件中被复制。在我们的例子中，我们仅仅像下面这样直接粘贴在头文件中：

```cpp
%module sample
%{
#include "sample.h"
%}
...
extern int gcd(int, int);
extern int in_mandel(double x0, double y0, int n);
extern int divide(int a, int b, int *remainder);
extern double avg(double *a, int n);

typedef struct Point {
    double x,y;
} Point;

extern double distance(Point *p1, Point *p2);
```

有一点需要强调的是这些声明会告诉 Swig 你想要在 Python 模块中包含哪些东西。 通常你需要编辑这个声明列表或相应的修改下它。 例如，如果你不想某些声明被包含进来，你要将它从声明列表中移除掉。

使用 Swig 最复杂的地方是它能给 C 代码提供大量的自定义操作。

第一个自定义是 `%extend` 指令允许方法被附加到已存在的结构体和类定义上。 在例子中，这个被用来添加一个 Point 结构体的构造器方法。 它可以让你像下面这样使用这个结构体：

```python
>>> p1 = sample.Point(2,3)
```

如果略过的话，Point 对象就必须以更加复杂的方式来被创建：

```python
>>> # Usage if %extend Point is omitted
>>> p1 = sample.Point()
>>> p1.x = 2.0
>>> p1.y = 3
```

第二个自定义涉及到对 `typemaps.i` 库的引入和 `%apply` 指令， 它会指示 Swig 参数签名 `int *remainder` 要被当做是输出值。 这个实际上是一个模式匹配规则。 在接下来的所有声明中，任何时候只要碰上 `int *remainder` ，他就会被作为输出。 这个自定义方法可以让 `divide()` 函数返回两个值。

```python
>>> sample.divide(42,8)
[5, 2]
```

最后一个涉及到 `%typemap` 指令的自定义可能是这里展示的最高级的特性了。 一个 typemap 就是一个在输入中特定参数模式的规则。 在本节中，一个 typemap 被定义为匹配参数模式 `(double *a, int n)` . 在 typemap 内部是一个 C 代码片段，它告诉 Swig 怎样将一个 Python 对象转换为相应的 C 参数。 本节代码使用了 Python 的缓存协议去匹配任何看上去类似双精度数组的输入参数 （比如 NumPy 数组、array 模块创建的数组等），更多请参考 15.3 小节。

在 typemap 代码内部，`$1` 和 `$2` 这样的变量替换会获取 typemap 模式的 C 参数值 （比如 `$1` 映射为 `double *a` ）。`$input` 指向一个作为输入的 `PyObject *` 参数， 而 `$argnum` 就代表参数的个数。

编写和理解 typemaps 是使用 Swig 最基本的前提。 不仅是说代码更神秘，而且你需要理解 Python C API 和 Swig 和它交互的方式。 Swig 文档有更多这方面的细节，可以参考下。

不过，如果你有大量的 C 代码需要被暴露为扩展模块。 Swig 是一个非常强大的工具。关键点在于 Swig 是一个处理 C 声明的编译器， 通过强大的模式匹配和自定义组件，可以让你更改声明指定和类型处理方式。 更多信息请去查阅 [Swig网站](http://www.swig.org/) ， 还有 [特定于Python的相关文档](http://www.swig.org/Doc2.0/Python.html)。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://l1nwatch.gitbook.io/python-cookbook/di-15-zhang-c-yu-yan-kuo-zhan/15.9-yong-wsig-bao-zhuangcdai-ma.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
