SWIG学习总结(翻译,本身理解为辅)

原文地址 http://program.upc.edu.cn/kaitian/?p=65node

SWIG介绍python

SWIG (Simplified Wrapper and Interface Generator) ,即简化包以及接口生成器,为脚本语言(tcl,perl,python等)提供了C和C++的接口。SWIG在1995年在Los Alamos National Laborator为开发一个用户接口应运而生,个人毕业设计外文翻译是其中一个科学家写的论文,在这里能够看到。SWIG把科学家从繁杂的编写接口的工做中解脱出来,使他们把更多的时间投入到更加剧要的部分上去。注意的是,貌似Google App Enginer也在用SWIG。一样的功能能够用C API ,CTypes,和C++的Boost库实现,本文介绍SWIG的使用。c++

SWIG是一个编译器,能够处理C++的声明等。windows

WINDOWS下,能够用VC或者是VS配置SWIG和Python,这篇文章写的尽详细,尤为是windows的用户能够参考下。下载swig的windows版,为swig.exe增长环境变量,另外还要下载一个MinGW,或者是cygwin编译C或者是C++文件。数组

脚本语言如何处理C的程序?安全

脚本语言解释器能够经过高级控制来使用,底层的C++程序能够经过特殊的脚本语言命令来调用。脚本语言先把C++的原始函数转化为特殊的wrapper函数,其能够做为C++和脚本语言的胶水。Wrapper作了以下几件事:
1,收集函数参数确保他们都有效
2,调用C函数,
3 把函数的返回值转化为脚本语言能够识别的形式。app

建立完wrapper函数,最后一步就是告诉脚本语言你这个函数。和通常就是在模块加载时在初始化函数里面实现。比方说,你建立了一个函数factor,那么系统就会为你增长一个factor的命令。函数

变量连接翻译

Foo = 4设计

A = foo + 2.0

Foo = a + 2

为了实现这两个操做,变量通常经过一组getset函数来操纵。党变量被读取时,就调用get函数;变量值被改变时,set函数被调用。

常量:

#define RED   0xff0000,

为了确保变量可见,值在脚本语言中以RED的格式显示;实际上,脚本语对建立变量的支持已经很完美了。

类和结构体

脚本语言在处理简单的函数和变量时毫无压力,可是在处理类和变量时存在一些问题。最直接的技术就是去构造一些配套函数来隐藏一个结构体的底层展现。

struct Vector {
            Vector();
            ~Vector();
            double x,y,z;
};

被转化成以下几个函数

Vector *new_Vector();
void delete_Vector(Vector *v);
double Vector_x_get(Vector *v);
double Vector_y_get(Vector *v);
double Vector_z_get(Vector *v);
void Vector_x_set(Vector *v, double x);
void Vector_y_set(Vector *v, double y);
void Vector_z_set(Vector *v, double z);

这些函数为获取object提供了解决机制,因此解释器不须要知道Vector的具体底层实现是什么(调用这些函数便可)

5.SWIG基础

这一节描述了SWIG的基础操做,输入文件的结构,如何处理标准C声明,

SWIG的命令:swig [ options ] filename
Swig –help能够得到SWIG的选项命令

输入格式:

SWIG指望文件包含标准CC++的声明,一个SWIG文件通常以.i或者是.swg为后缀。某些状况下,SWIG能够直接做为i源文件或者是图文件,可是这不是很标准的方式,这里有几个你不应这么作的缘由。

SWIG标准输入文件;

%module mymodule
%{
#include "myheader.h"
%}
// Now list ANSI C/C++ declarations
int foo;
int bar(int x);
...

%module表示了模块名字。

%{………….%}块里的东西直接被拷贝到SWIG建立的wrapper文件中。

这个section通常包括了头文件和一些声明,使wrapper文件正常编译。SWIG不去解析,也不去解释这个块。

SWIG输出文件:

SWIG的输出是CC++文件,包含了创建扩展模块须要的全部的wraper代码。根据目标语言,SWIG也会生成一些额外的文件。缺省状态下,file.i会被转化为file_wrap.c或者是file_wrap.cxx(取决于你是否使用了-c++选项)。输出文件名能够用-o选项来改变。

SWIG是一个很强大的编译器,你没有必要去修改输出文件。为了创建最终的扩展单元, SWIG输出文件和你其他的C++文件一块儿被编译连接,最终生成一个共享库。

C预处理器

像C语言,SWIG经过C预处理器的加强版原本预处理全部的入文件,全部标准的预处理特性都是支持的,包括文件包含,条件编译,和宏定义

基本数据类型处理

当一个整数从C语言传来时, 会有一些类型转换使它变为目标语言的格式。所以,16位的short可能被提高为32位整形。当整数太大时,它悄悄地被截断了。

有一个例外,就是unsigned char和signed char,他们被认为是8位的整形,是一个8位的ASCII码字符。当从脚本语言传递一个字符串给他时,他会自动街区第一个来做为这个字符。好比说,“foo”会被认为是字符’f’.。

若是目标语言没有bool类型,那么bool会被转化为0或者是1。

当使用大整数值的时候要特别注意,大部分脚本语言使用32位整数,因此64位整数可能会致使截断错误;一样的问题可能在unsigned int上发生(他可能会变为一个很大的负数)。

Int,char,short是很安全的,可是对于unsigned和long,你必须在SWIGwrap它提早检查

你源程序里的操做。

Char*类型被做为NULL终止的ASCII码字符串处理,SWIG在目标语言中把俺们映射为8位的字符串。SWIG在字符串送给C++以前,已经把它们做为NULL终止的字符串处理,因此传递二进制数据很不适合的。可是经过定义一个SWIG的typemaps就能够很好的解决这个问题。

常量

常量能够经过#define,枚举和%constant来定义,

注意define具备传递性

#define PI 3.1415

#define PI_4 PI/4

PI_4 也是一个常量!

注意,枚举类型必定在接口文件中有所体现才能够。

Char*的一点小提醒

当字符串从脚本语言传递到C++的char*时,这个指针一般指向一个解释器内部的区域,而修改这块区域是很不明智的行为。并且,一些语言是明令禁止这样作的,比方说python的string对象是不可变的。

问题的来源是一些可能改变字符串数据的函数,

char *strcat(char *s, const char *t)

SWIG会为他生成一个wrapper,可是其行为时未定义的,会致使你的程序崩溃,段错误或者是其余内存问题。缘由就是s指向了目标语言的一块内部区域,而这些数据你是不应碰的。

最后一行:不要依赖char*,可是经过使用SWIG的typemaps能够改变这种行为。

指针和复杂的对象

绝大部分C程序都操纵数组,结构体和其余类型的对象,

5.3.1简单指针

Int*    char** double*** 这种指针所有被SWIG支持。SWIG没有把指针指向的对象变为脚本语言的格式,SWIG只是把指针转变为他的真实值。

SWIG处理指针是不透明的, 所以,一个指针能够从一个函数那里返回获得,也能够把它传递给另外一个C函数。脚本语言的指针不能被直接操纵,虽然指针看上去是16进制的地址,并且,SWIG不把指针转换为更高级的对象,好比说数组和列表

派生类,结构体和类,对于他们来讲,SWIG有一个很是简单的规则:全部对象都是指针,换句话说,SWIG经过引用来操纵object。不少C++程序大量使用指针,SWIG能够很好的处理指针与对象。

举个例子,

FILE *fopen(char *, char *);
int fclose(FILE *);

SWIG并不知道FILE是什么东东,可是既然他是个指针,因此他是什么就不是哪么重要了,若是把这段代码包装成python,

F1 = open(“1.txt”,”r”)

Fclose(f1)

程序在并不知道FILE是啥的状况下依然可以工做正常。

没有定义的类型

当SWIG遇到一个不认识的类型时,他认为它是一个结构体或者是一个类,

void matrix_multiply(Matrix *a, Matrix *b, Matrix *c);

SWIG不知道Matrix是啥,可是很明显他是指向一个对象的指针,因此SWIG将他包装为一个指针。

不像C++,SWIG在使用一个变量以前不会去检查他是否被定义过,这个特性可使SWIG经过有限的信息生成接口。

这样,全部没被声明的类型都被认为是指向结构体或者是类的指针,eg:

void foo(size_t num);

若是size_t没有定义的话,SWIG会把他当作size_t*,因此接口脚本有事会显得莫名其妙:

            foo(40);
TypeError: expected a _p_size_t.

解决这个问题惟一的方法就是提早声明size_t

Typedef

C++用typedef来定义新的类型,typedef unsigned int size_t

Typedef必须在头文件或者是声明部分定义,SWIG会跟踪typedef的声明,时时刻刻根据这个信息来进行类型检查,

void foo(unsigned int *ptr);

他能够接受 size_t或者是unsigned int

经过值来传递结构体

double dot_product(Vector a, Vector b);

哪么SWIG又是怎么处理的呢?它使用指针把这个函数转化为下面格式:

double wrap_dot_product(Vector *a, Vector *b) {
    Vector x = *a;
    Vector y = *b;
    return dot_product(x,y);
}

目标语言用dot_product,如今他接受的是指针,而不是一个vector对象,大部分状况下,转变是透明的,因此你不必定知道。

值返回

Vector cross_product(Vector v1, Vector v2);

SWIG是这样作滴:

Vector *wrap_cross(Vector *v1, Vector *v2) {
        Vector x = *v1;
        Vector y = *v2;
        Vector *result = new Vector(cross(x,y)); // Uses default copy constructor
        return result;
}

SWIG分配了一个新对象,返回这个对象的引用,这个对象再也不使用时,用户能够将其删除。很明显,若是你没注意到隐式的内存分配,就会有内存泄露。

结构体变量的连接

相似于Vector unit_i;

这样的变量,都会被对应以下函数:

Vector *unit_i_get() {
            return &unit_i;
}
void unit_i_set(Vector *value) {
            unit_i = *value;
}

Char*的连接

若是你有一个变量,相似于char *foo;

SWIG会生成以下代码:

void foo_set(char *value) {
   if (foo) delete [] foo;
   foo = new char[strlen(value)+1];
   strcpy(foo,value);
}

数组

SWIG彻底支持数组,他也把数组当作指针。

int foobar(int a[40]);
void grok(char *argv[]);
void transpose(double a[20][20]);

会被当作

int foobar(int *a);
void grok(char **argv);
void transpose(double (*a)[20]);

能够注意到,SWIG也不作数组范围检查。

数组变量时提供的,可是缺省是只读状态。

int   a[100][200];
读取变量a,会获得a[0][0]的地址,任何试图改变a的行为都会被认为是个错误,由于SWIG不知道怎么从目标语言拷贝值到数组。为了绕过这个限制,你能够写一个简单的帮助函数
%inline %{
void a_set(int i, int j, int val) {
   a[i][j] = val;
}
int a_get(int i, int j) {
   return a[i][j];
}
%}

字符串数组呗SWIG特殊处理,目标串能够在数组中储存,eg

char pathname[256];
SWIG会为其生成以下几个函数
char *pathname_get() {
   return pathname;
}
void pathname_set(char *value) {
   strncpy(pathname,value,256);
}

函数指针和回调函数

SWIG支持函数指针,可是是C的函数而不是目标语言的函数。

若是有一个函数:int binary_op(int a, int b, int (*op)(int,int));

在python中 def add(a, b):return a + b

观察下binary_op会发生什么:应该是个错误吧,由于SWIG不知道怎么把python映射为C的回调函数。可是只要在接口文件中声明

%constant int add(int a, int b);便可

注意,constant必不可少~~~

Unfortunately, by declaring the callback functions as constants, they are no longer accessible as functions. For example:

不幸的是,一旦声明回调函数是和常量,他不再能做为函数被调用。

结构体和联合变量

当SWIG碰见结构体或者是联合,就会建立一些配套函数;若是没有构造函数和析构函数,他会在接口文件中建立他们。

注意要在接口文件中写上结构体的声明,而且python中结构体的成员能够任意扩张,好比说,在C函数中,node只有a,b,cc三个变量,可是python程序中你能够任意给一个对象加上任意变量名,只要你愿意!

构造函数和析构函数

对于建立和销毁物体有一个很好的机制是颇有用的,SWIG会自动写一个函数若是没有的话。

接口文件编写策略

SWIG不须要你的代码作出任何改变,可是若是你给SWIG一大坨原始的C头文件和源代码,结果可能不是你想的那样好-事实上,这太可怕了!下面是编写C的接口文件遵循的步骤:

  1. 肯定你想包装的函数。把全部函数都包装通常是没必要要的,因此一些未雨绸缪能够简化接口,C头文件是寻找声明的好地方。
  2. 为你的程序新建一个新的接口文件
  3. 把那些生命拷贝文件里,或者是使用SWIG的 %include,  去包含整个头文件。
  4. 明确全部的语法都是符合标准C++的
  5. 明确全部typedef和其余类型信息在接口文件里都是可见的。另外,确保类型信息出现的次序是正确的。更为重要的是,在使用变量值钱,必定要定义它!
  6. 若是你的程序有main函数,记得给他改的名字吧
  7. 运行SWIG,编译

关于main()函数

当你程序有main函数的时候,必定要删除或者重命名。绝大部分脚本语言都有本身的main函数程序。而且main在动态加载的时候也没啥用。

  1. 彻底删除main函数
  2. 重命名
  3. 使用条件编译,只有再也不使用脚本语言是才包含main函数。

彻底删除main函数会产生潜在的初始化问题,你能够考虑一个函数叫作progran_init()什么的,他能够在程序开始以前调用。

注意:

一些状况下,你会为一些脚本语言包装main函数,若是你这样作了,编译可能会进行,你的模块也能够成功加载。惟一的问题就是当你调用main函数时,你会发现,实际上它调用的是脚本语言本身的main函数!!!