4 Scripting Languages && 5 SWIG Basics

4 Scripting Languages

  本章简要概述了脚本语言扩展编程以及脚本语言解释器访问C和c++代码的机制。

4.1 The two language view of the world

  当使用一种脚本语言来控制一个C程序时,得到的系统看起来是这样的:

  

 

 

   在这种编程模型中,脚本语言解释器用于高级控制,而C/ c++程序的底层功能是通过特殊的脚本语言“命令”来访问的。如果您曾经尝试过编写自己的简单命令解释器,您可能会把脚本语言方法看作是它的高级实现。同样地,如果您曾经使用过MATLAB或IDL之类的包,它也是一个非常类似的模型——解释器执行用户命令和脚本。然而,大多数底层功能是用低级语言编写的,比如C或Fortran。

  计算的两种语言模型非常强大,因为它利用了每种语言的优势。C/ c++可以用于最大的性能和复杂的系统编程任务。脚本语言可以用于快速原型化、交互式调试、脚本编写和访问高级数据结构(如关联数组)。

4.2 How does a scripting language talk to C?

  脚本语言是围绕知道如何执行命令和脚本的解析器构建的。在这个解析器中,有一个执行命令和访问变量的机制。通常,这用于实现该语言的内置特性。然而,通过扩展解释器,通常可以添加新的命令和变量。为此,大多数语言都定义了一个特殊的API来添加新的命令。此外,一个特殊的外部函数接口定义了如何将这些新命令挂接到解释器中。

  通常,当你向脚本解释器添加一个新命令时,你需要做两件事;首先,您需要编写一个特殊的“包装器”函数,作为解释器和底层C函数之间的粘合剂。然后,您需要通过提供关于函数名、参数等的详细信息,向解释器提供关于包装器的信息。下面几节将说明这个过程。

4.2.1 Wrapper functions

  假设你有一个像这样的普通C函数:

1 int fact(int n) {
2   if (n <= 1)
3     return 1;
4   else
5     return n*fact(n-1);
6 }

  为了从脚本语言中访问这个函数,有必要编写一个特殊的“包装器”函数,作为脚本语言和底层C函数之间的粘合剂。包装器函数必须做三件事:

  • Gather function arguments and make sure they are valid.
  • Call the C function.
  • Convert the return value into a form recognized by the scripting language.

  例如,上面例子中的fact()函数的Tcl包装器函数可能如下所示:

int wrap_fact(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[]) {
  int result;
  int arg0;
  if (argc != 2) {
    interp->result = "wrong # args";
    return TCL_ERROR;
  }
  arg0 = atoi(argv[1]);
  result = fact(arg0);
  sprintf(interp->result, "%d", result);
  return TCL_OK;
}

  创建包装器函数之后,最后一步是告诉脚本语言关于新函数的信息。这通常在加载模块时由语言调用的初始化函数中完成。例如,将上述函数添加到Tcl解释器需要如下代码:

int Wrap_Init(Tcl_Interp *interp) {
  Tcl_CreateCommand(interp, "fact", wrap_fact, (ClientData) NULL,
                    (Tcl_CmdDeleteProc *) NULL);
  return TCL_OK;
}

  在执行Tcl时,现在将有一个名为“fact”的新命令,您可以像使用任何其他Tcl命令一样使用它。

  虽然已经说明了向Tcl添加新函数的过程,但Perl和Python的过程几乎相同。两者都需要编写特殊的包装器,并且都需要额外的初始化代码。只是具体细节不同。

4.2.2 Variable linking

  变量链接是指将C/ c++全局变量映射到脚本语言解释器中的变量的问题。例如,假设你有以下变量:

double Foo = 3.5;

  它可能是很好的访问它从一个脚本如下(显示为Perl):

$a = $Foo * 2.3;   # Evaluation
$Foo = $a + 2.0;   # Assignment

  为了提供这种访问,通常使用一对get/set函数对变量进行操作。例如,每当读取一个变量的值时,就会调用一个“get”函数。类似地,每当一个变量的值改变时,就会调用一个“set”函数。

  在许多语言中,对get/set函数的调用可以附加到求值和赋值操作符。因此,对一个变量(如$Foo)求值可能会隐式地调用get函数。类似地,键入$Foo = 4将调用底层的set函数来更改值。

4.2.3 Constants

  在许多情况下,C程序或标准库可能定义一个大型常量集合。例如:

#define RED   0xff0000
#define BLUE  0x0000ff
#define GREEN 0x00ff00

  为了使常量可用,可以将它们的值存储在脚本语言变量中,如$RED、$BLUE和$GREEN。几乎所有的脚本语言都提供了C函数来创建变量,所以安装常量通常是一个简单的练习。

4.2.4 Structures and classes

  虽然脚本语言在访问简单的函数和变量方面没有问题,但是访问C/ c++结构和类则存在不同的问题。这是因为结构的实现在很大程度上与数据表示和布局的问题有关。此外,某些语言特性很难映射到解释器。例如,在Perl接口中c++继承意味着什么?

  处理结构最直接的技术是实现一组隐藏结构底层表示的访问器函数。例如,

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);

  现在,从解释器中可以这样使用这些函数:

% set v [new_Vector]
% Vector_x_set $v 3.5
% Vector_y_get $v
% delete_Vector $v
% ...

  由于访问器函数提供了一种访问对象内部的机制,解释器不需要知道Vector的实际表示形式。

4.2.5 Proxy classes

  在某些情况下,可以使用低级访问器函数来创建代理类,也称为影子类。代理类是一种特殊类型的对象,它在脚本语言中创建,以类似于原始结构的方式访问C/ c++类(或结构)(也就是说,它代理了真正的c++类)。例如,如果你有以下c++定义:

class Vector {
public:
  Vector();
  ~Vector();
  double x, y, z;
};

  代理类机制将允许您以更自然的方式从解释器访问结构。例如,在Python中,你可能想这样做:

>>> v = Vector()
>>> v.x = 3
>>> v.y = 4
>>> v.z = -13
>>> ...
>>> del v

  类似地,在Perl5中,你可能希望接口像这样工作:

$v = new Vector;
$v->{x} = 3;
$v->{y} = 4;
$v->{z} = -13;

  Finally, in Tcl :

Vector v
v configure -x 3 -y 4 -z -13

  在使用代理类时,实际上有两个对象在工作——一个在脚本语言中,另一个是底层的C/ c++对象。操作对这两个对象的影响是相等的,对于所有的实际目的,看起来就好像您只是在操作一个C/ c++对象。

4.3 Building scripting language extensions

  在C/ c++应用程序中使用脚本语言的最后一步是将扩展添加到脚本语言本身。有两种主要的方法可以做到这一点。首选的技术是以共享库的形式构建一个可动态加载的扩展。或者,您可以重新编译脚本语言解释器并将扩展添加到其中。

4.3.1 Shared libraries and dynamic loading

  要创建共享库或DLL,通常需要查看编译器和链接器的手动页面。然而,以下是一些常见平台的程序:

# Build a shared library for Solaris
gcc -fpic -c example.c example_wrap.c -I/usr/local/include
ld -G example.o example_wrap.o -o example.so

# Build a shared library for Linux
gcc -fpic -c example.c example_wrap.c -I/usr/local/include
gcc -shared example.o example_wrap.o -o example.so

  要使用共享库,只需在脚本语言中使用相应的命令(加载、导入、使用等等……)。这将导入您的模块并允许您开始使用它。例如:

% load ./example.so
% fact 4
24
%

  当使用c++代码时,构建共享库的过程可能会更加复杂——主要是因为c++模块可能需要额外的代码才能正确操作。在许多机器上,你可以按照上面的步骤构建一个共享的c++模块,但是将链接改为如下:

c++ -shared example.o example_wrap.o -o example.so

4.3.2 Linking with shared libraries

  当将扩展构建为共享库时,您的扩展依赖于机器上的其他共享库的情况并不少见。为了让扩展工作,它需要能够在运行时找到所有这些库。否则,可能会出现如下错误:

>>> import graph
Traceback (innermost last):
  File "<stdin>", line 1, in ?
  File "/home/sci/data1/beazley/graph/graph.py", line 2, in ?
    import graphc
ImportError:  1101:/home/sci/data1/beazley/bin/python: rld: Fatal Error: cannot 
successfully map soname 'libgraph.so' under any of the filenames /usr/lib/libgraph.so:/
lib/libgraph.so:/lib/cmplrs/cc/libgraph.so:/usr/lib/cmplrs/cc/libgraph.so:

  这个错误意味着SWIG创建的扩展模块依赖于一个名为“libgraph”的共享库。以至于系统无法定位。要解决这个问题,您可以采用以下几种方法。

  •   链接您的扩展并显式地告诉链接器所需库的位置。通常情况下,这可以通过一个特殊的链接器标志来实现,比如-R、-rpath等。这不是以标准方式实现的,因此请阅读链接器的手册页,以了解如何设置共享库的搜索路径的更多信息。
  •   将共享库放在与可执行文件相同的目录中。在非unix平台上进行正确操作有时需要这种技术。
  •   将UNIX环境变量LD_LIBRARY_PATH设置为运行Python之前共享库所在的目录。虽然这是一个简单的解决方案,但不推荐使用。考虑使用链接器选项来设置路径。

4.3.3 Static linking

  使用静态链接,可以使用扩展重新构建脚本语言解释器。这个过程通常包括编译一个简短的主程序,该程序将您定制的命令添加到语言中并启动解释器。然后将程序与库链接以生成新的脚本语言可执行文件。

  尽管所有平台都支持静态链接,但这并不是构建脚本语言扩展的首选技术。事实上,这样做的实际原因很少——请考虑使用共享库。

5 SWIG Basics

  本章描述SWIG的基本操作、其输入文件的结构以及如何处理标准ISO C声明。下一章将介绍c++的支持。然而,c++程序员仍然应该阅读本章以理解基本知识。关于每种目标语言的具体细节将在后面的章节中描述。

5.1 Running SWIG

  要运行SWIG,使用带有选项和文件名的SWIG命令,如下所示:

swig [ options ] filename

  其中filename是SWIG接口文件或C/ c++头文件。通过运行swig -help可以看到完整的帮助。下面是可以使用的常见选项集。还为每个目标语言定义了其他选项。可以通过运行swig -<lang> -help for language <lang>特定选项来获得完整的列表,例如swig - Ruby -help for Ruby。

Supported Target Language Options
     -csharp         - Generate C# wrappers
     -d              - Generate D wrappers
     -go             - Generate Go wrappers
     -guile          - Generate Guile wrappers
     -java           - Generate Java wrappers
     -javascript     - Generate Javascript wrappers
     -lua            - Generate Lua wrappers
     -octave         - Generate Octave wrappers
     -perl5          - Generate Perl 5 wrappers
     -php7           - Generate PHP 7 wrappers
     -python         - Generate Python wrappers
     -r              - Generate R (aka GNU S) wrappers
     -ruby           - Generate Ruby wrappers
     -scilab         - Generate Scilab wrappers
     -tcl8           - Generate Tcl 8 wrappers
     -xml            - Generate XML wrappers
一般选择
-addextern-添加额外的外部声明
C++实现的C++处理
-co<file>-从SWIG库中签出<file>
-copyctor-尽可能自动生成复制构造函数
-cpperraswarn-将预处理器错误语句视为警告(默认)
-CPPXEXEX> -将生成的C++文件的文件扩展名改为< Ext>
(默认为cxx)
-版权-展示版权声明
-调试类-显示有关在接口中找到的类的信息
-调试模块<n>-在阶段1-4显示模块解析树,<n>是阶段的csv列表
-调试符号选项卡-显示符号表信息
-调试符号-在符号表中显示目标语言符号
-调试C符号-在符号表中显示C符号
-调试lsymbols-显示目标语言层符号
-调试标记-显示有关界面中找到的标记的信息
-调试模板-显示调试模板的信息
-调试顶部<n>-在阶段1-4显示整个解析树,<n>是阶段的csv列表
-debug typedef-在界面中显示有关类型和typedef的信息
-调试类型映射-显示类型映射调试信息
-调试tmsearch-显示类型映射搜索调试信息
-debug tmused-显示用于调试信息的类型映射
-控制器-为所有类打开控制器模式,主要用于测试
-dirprot-为控制器类打开受保护成员的包装(默认)
-D<symbol>-定义符号<symbol>(用于条件编译)
-仅电子预处理,不生成包装器代码
-外部运行时[文件]-导出SWIG运行时堆栈
-伪造版本<v>-使SWIG将程序版本号伪造为<v>
-fcompact-在压缩模式下编译
-功能<列表>-设置全局功能,其中<列表>是以逗号分隔的
功能,例如-功能控制器,autodoc=1
如果未为特征指定明确值,则使用默认值1
-fastdispatch-启用快速调度模式以生成更快的过载调度程序代码
-Fmicrosoft-以Microsoft格式显示错误/警告消息
-Fstandard-以常用格式显示错误/警告消息
-虚拟消除模式下的虚拟编译
-帮助-显示帮助
-我--不搜索当前目录
-I<dir>-在目录<dir>
-ignoremissing-忽略缺少的包含文件
-importall-遵循所有#include语句作为导入
-includeall-遵循all#include语句
-l<ifile>-包括SWIG库文件<ifile>
-宏错误-报告宏中的错误
-makedefault-创建默认构造函数/析构函数(默认值)
-列出所有依赖项
-MD-相当于`-M-MF<file>”,只是没有暗示`-E'
-MF<file>-在<file>中生成依赖项并继续生成包装器
-MM-列出依赖项,但忽略SWIG库中的文件
-MMD类似于`-MD',但省略SWIG库中的文件
-模块<name>-将模块名称设置为<name>
-MP-为所有依赖项生成虚假目标
-MT<target>-设置依赖项生成发出的规则的目标
-无合同-关闭合同检查
-nocpperraswarn-不要将预处理器错误语句视为警告
-nodefault-不生成默认构造函数或默认析构函数
-nodefaultctor-不生成隐式默认构造函数
-nodefaultdtor-不生成隐式默认析构函数
-nodirprot-不包装受控制器保护的成员
-noexcept-不包装异常说明符
-nofastdispatch-禁用快速调度模式(默认)
-nopreprocess-跳过预处理器步骤
-notemplatereduce-禁用模板中TypeDef的缩减
-O-启用优化选项:
-fastdispatch-fvirtual
-o<outfile>-将C/C++输出文件的名称设置为<outfile>
- OH <头文件> -为导演指向<文件>的C++输出头文件的设置名称>
-outcurrentdir-将默认输出目录设置为当前目录,而不是输入文件的路径
-outdir<dir>-将特定语言的文件输出目录设置为<dir>
-PCRE恢复-显示PCRE版本信息
-小型-在虚拟消除和压缩模式下编译
-swiglib-报告SWIG库的位置并退出
-templatereduce-减少模板中的所有typedef
-v-在详细模式下运行
-版本-显示SWIG版本

  参数也可以在命令行选项文件(也称为响应文件)中传递,如果它们超过了系统命令行长度限制,这将非常有用。为此,将参数放入文件中,然后提供以@作为前缀的文件名,如下所示:

swig @file

  将从文件中读取的选项插入到文件选项中。如果文件不存在,或者无法读取,那么将按字面意思处理该选项,而不会删除该选项。

  文件中的选项用空格分隔。一个空白字符可以通过将整个选项括在单引号或双引号中来包含在一个选项中。任何字符(包括反斜杠)都可以通过在要包含的字符前面加上反斜杠来包含。文件本身可能包含额外的@file选项;任何这样的选项都将被递归地处理。

5.1.1 Input format

  作为输入,SWIG需要一个包含ISO C/ c++声明和特殊SWIG指令的文件。通常情况下,这是一个特殊的SWIG接口文件,通常用特殊的.i或.swg后缀表示。在某些情况下,SWIG可以直接用于原始头文件或源文件。然而,这不是最典型的情况,您可能不希望这样做的原因有几个(稍后介绍)。

  最常见的SWIG接口格式如下:

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

  模块名是使用特殊的%module指令提供的。模块在模块介绍部分有进一步的描述。

  在%{…%}块被逐字复制到由SWIG创建的结果包装器文件中。这一部分几乎总是用于包含头文件和其他声明,这些声明是编译生成的包装器代码所必需的。需要强调的是,仅仅因为您在SWIG输入文件中包含了声明,该声明不会自动出现在生成的包装器代码中——因此您需要确保在%{…%}部分。需要注意的是,包含在%{…%}不被解析或解释

5.1.2 SWIG Output

  SWIG的输出是一个C/ c++文件,其中包含构建扩展模块所需的所有包装器代码。SWIG可能会根据目标语言生成一些额外的文件。默认情况下,一个名为file的输入文件。I被转换为文件file_wrap.c或file_wrap。(取决于是否使用了-c++选项)。输出的C/ c++文件的名称可以使用-o选项更改。在某些情况下,编译器使用文件后缀来确定源语言(C、c++等)。因此,如果您希望使用不同于默认值的后缀,则必须使用-o选项来更改swig生成的包装文件的后缀。例如

swig -c++ -python -o example_wrap.cpp example.i

  SWIG创建的C/ c++输出文件通常包含为目标脚本语言构建扩展模块所需的所有内容。SWIG不是存根编译器,通常也不需要编辑输出文件(如果查看输出,您可能不希望这样做)。要构建最后一个扩展模块,将编译SWIG输出文件,并将其与C/ c++程序的其余部分链接,以创建一个共享库。

  对于许多目标语言,SWIG也会用目标语言生成代理类文件。这些特定于语言的文件的默认输出目录与生成的C/ c++文件的目录相同。这可以使用-outdir选项进行修改。例如:

swig -c++ -python -outdir pyfiles -o cppfiles/example_wrap.cpp example.i

  如果存在cppfiles和pyfiles目录,则会生成如下内容:

cppfiles/example_wrap.cpp
pyfiles/example.py

  如果使用了-outcurrentdir选项(不使用-o),那么SWIG的行为就像一个典型的C/ c++编译器,默认的输出目录就是当前目录。如果没有这个选项,默认的输出目录就是输入文件的路径。如果-o和-outcurrentdir同时使用,-outcurrentdir实际上会被忽略,因为如果没有使用-outdir覆盖,语言文件的输出目录与生成的C/ c++文件的目录相同。

5.1.3 Comments

  C和c++风格的注释可以出现在接口文件的任何位置。在先前版本的SWIG中,注释用于生成文档文件。然而,该特性目前正在修复中,并将在稍后的SWIG版本中重新出现。

5.1.4 C Preprocessor

  与C类似,SWIG通过增强的C预处理器对所有输入文件进行预处理。支持所有标准的预处理功能,包括文件包含、条件编译和宏。但是,除非提供了-includeall命令行选项,否则#include语句将被忽略。禁用的原因包括:SWIG有时用于处理原始C头文件。在这种情况下,您通常只希望扩展模块包含提供的头文件中的函数,而不是该头文件可能包含的所有内容(例如,系统头文件、C库函数等)。

  还应该注意的是,SWIG预处理程序跳过了包含在%{…%}。此外,该预处理器还包括许多宏处理增强功能,这使得它比普通C预处理器更强大。这些扩展在“预处理器”一章中描述。

5.1.5 SWIG Directives

  WIG的大多数操作都由特殊的指令控制,这些指令的前面总是有一个“%”,以区别于普通的C声明。这些指令用于提供SWIG提示或以某种方式改变SWIG的解析行为。

  由于SWIG指令不是合法的C语法,所以通常不可能在头文件中包含它们。但是,SWIG指令可以通过下面这样的条件编译被包含在C头文件中:

/* header.h  --- Some header file */

/* SWIG directives -- only seen if SWIG is running */ 
#ifdef SWIG
%module foo
#endif

  SWIG是SWIG在解析输入文件时定义的特殊预处理符号。

5.1.6 Parser Limitations

  尽管SWIG可以解析大多数C/ c++声明,但它没有提供完整的C/ c++解析器实现。这些限制中的大多数适用于非常复杂的类型声明和某些高级c++特性。具体来说,以下特性目前不受支持:

  非常规类型声明。例如,SWIG不支持如下声明(尽管这是合法的C):

/* Non-conventional placement of storage specifier (extern) */
const int extern Number;

/* Extra declarator grouping */
Matrix (foo);    // A global variable

/* Extra declarator grouping in parameters */
void bar(Spam (Grok)(Doh));

  实际上,很少(如果有的话)C程序员会真正编写这样的代码,因为编程书籍中从未介绍过这种风格。然而,如果您感到特别困惑,您当然可以打破SWIG(尽管为什么要这样做呢?)

  不建议在c++源文件(.C、.cpp或.cxx文件中的代码)上运行SWIG。通常的方法是提供SWIG头文件来解析c++定义和声明。主要原因是,如果SWIG解析作用域定义或声明(对于c++源文件来说,这是正常的),它将被忽略,除非先前解析了符号的声明。例如

/* bar not wrapped unless foo has been defined and 
   the declaration of bar within foo has already been parsed */
int foo::bar(int) {
  ... whatever ...
}

  c++的某些高级特性,如嵌套类,还没有得到完全支持。有关更多信息,请参阅c++嵌套类部分。

  在解析错误的情况下,可以使用条件编译跳过有问题的代码。例如:

#ifndef SWIG
... some bad declarations ...
#endif

  或者,您可以直接从接口文件中删除有问题的代码。

  SWIG没有提供一个完整的C + +的解析器实现的原因之一,它被设计来处理不完整的规范和非常宽容的处理C / c++数据类型(例如,SWIG可以生成接口,即使有失踪的类声明或不透明的数据类型)。不幸的是,这种方法使得实现C/ c++解析器的某些部分极其困难,因为大多数编译器使用类型信息来帮助解析更复杂的声明(对于真正好奇的人,实现中的主要复杂性是SWIG解析器没有像K&R)第234页中描述的那样使用单独的类型定义-名称终端符号。

5.2 Wrapping Simple C Declarations

  SWIG通过创建与在C程序中使用声明的方式紧密匹配的接口来包装简单的C声明。例如,考虑下面的接口文件:

%module example

%inline %{
extern double sin(double x);
extern int strcmp(const char *, const char *);
extern int Foo;
%}
#define STATUS 50
#define VERSION "1.1"

  在这个文件中,有两个函数sin()和strcmp(),一个全局变量Foo,以及两个常量STATUS和VERSION。当SWIG创建扩展模块时,可以分别以脚本语言函数、变量和常量的形式访问这些声明。in Python:

>>> example.sin(3)
5.2335956
>>> example.strcmp('Dave', 'Mike')
-1
>>> print example.cvar.Foo
42
>>> print example.STATUS
50
>>> print example.VERSION
1.1

  只要有可能,SWIG会创建一个与底层C/ c++代码紧密匹配的接口。然而,由于语言、运行时环境和语义之间的细微差别,并不总是能够做到这一点。接下来的几个部分将描述这种映射的各个方面。

5.2.1 Basic Type Handling

  为了构建接口,SWIG必须将C/ c++数据类型转换为目标语言中的等效类型。通常,脚本语言提供的基本类型集比c语言更有限。因此,这个转换过程涉及一定数量的类型强制转换。

  大多数脚本语言提供单一的整数类型,在C语言中使用int或long数据类型实现。下面的列表显示了SWIG将在目标语言中转换为或转换为整数的所有C数据类型:

int
short
long
unsigned
signed
unsigned short
unsigned long
unsigned char
signed char
bool

  当整型值从C转换时,使用强制转换将其转换为目标语言中的表示形式。因此,在C语言中短16位可以提升为32位整数。当整数向另一个方向转换时,值被转换回原来的C类型。如果值太大而无法容纳,则会静默地截断它。

  Unsigned char和signed char是作为8位整数处理的特殊情况。通常,char数据类型被映射为一个单字符的ASCII字符串。

  除非目标语言提供特殊的布尔类型,否则bool数据类型将被转换为或转换为0和1的整数值。

  在处理大整数值时需要注意一些问题。大多数脚本语言使用32位整数,因此映射64位长整数可能会导致截断错误。类似的问题也可能出现在32位无符号整数(可能以较大的负数出现)上。根据经验,int数据类型以及char和short数据类型的所有变体都可以安全使用。对于unsigned int和long数据类型,在用SWIG包装程序之后,需要仔细检查程序的正确操作。

  尽管SWIG解析器支持long - long数据类型,但并非所有语言模块都支持它。这是因为long long通常超过目标语言中可用的整数精度。在某些模块中,如Tcl和Perl5,长整数被编码为字符串。这允许表示这些数字的全部范围。但是,它不允许在算术表达式中使用long long值。还应该注意的是,虽然long long是ISO C99标准的一部分,但并不是所有C编译器都支持它。在尝试在SWIG中使用这种类型之前,请确保您使用的编译器支持long - long。

  SWIG识别以下浮点类型:

float
double

  浮点数被映射到目标语言中浮点数的自然表示,或者从浮点数映射出来。这几乎总是一个C double。SWIG不支持很少使用的long double数据类型。

  char数据类型被映射为一个带有单个字符的以NULL结尾的ASCII字符串。在脚本语言中使用时,它显示为包含字符值的小字符串。当将值转换回C时,SWIG从脚本语言中获取一个字符串并删除第一个字符作为字符值。因此,如果值“foo”被赋给一个char数据类型,它会得到值“f”。

  char *数据类型被处理为以null结束的ASCII字符串。SWIG将其映射为目标脚本语言中的8位字符串。SWIG在将字符串传递到C/ c++之前,将目标语言中的字符串转换为以NULL结束的字符串。这些字符串的默认处理不允许它们具有内嵌的NULL字节。因此,char *数据类型通常不适用于传递二进制数据。但是,可以通过定义SWIG类型映射来更改这种行为。有关这方面的详细信息,请参见Typemaps一章。

  目前,SWIG对Unicode和宽字符字符串(C语言的wchar_t类型)提供了有限的支持。有些语言为wchar_t提供了类型映射,但请记住,这些类型映射可能无法在不同的操作系统之间移植。这是一个微妙的主题,许多程序员对它的理解很差,并且没有在不同语言之间以一致的方式实现。对于那些提供Unicode支持的脚本语言,Unicode字符串通常以8位表示形式提供,例如UTF-8,可以映射为char *类型(在这种情况下,SWIG接口可能会工作)。如果您包装的程序使用Unicode,则无法保证目标语言中的Unicode字符将使用相同的内部表示(例如,UCS-2 vs. UCS-4)。您可能需要编写一些特殊的转换函数。

5.2.2 Global Variables

  只要有可能,SWIG会将C/ c++全局变量映射到脚本语言变量中。例如,

%module example
double foo;

  结果是这样的脚本语言变量:

# Python
cvar.foo = 3.5                  # Set foo to 3.5
print cvar.foo                  # Print value of foo

  无论何时使用脚本语言变量,都会访问底层的C全局变量。尽管SWIG尽一切努力使全局变量像脚本语言变量一样工作,但并不总是能够这样做。例如,在Python中,所有全局变量都必须通过一个称为cvar的特殊变量对象(如上所示)来访问。

  最后,如果将全局变量声明为const,则它只支持只读访问。注意:这个行为在SWIG-1.3中是新的。早期版本的SWIG错误地处理了const而创建了常量。

5.2.3 Constants

  可以使用#define、枚举或特殊的%constant指令来创建常量。下面的接口文件显示了一些有效的常量声明:

#define I_CONST       5               // An integer constant
#define PI            3.14159         // A Floating point constant
#define S_CONST       "hello world"   // A string constant
#define NEWLINE       '\n'            // Character constant

enum boolean {NO=0, YES=1};
enum months {JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG,
             SEP, OCT, NOV, DEC};
%constant double BLAH = 42.37;
#define PI_4 PI/4
#define FLAGS 0x04 | 0x08 | 0x40

  在#define声明中,常量的类型是通过语法推断的。例如,一个带小数点的数被假定为浮点数。此外,为了实际创建一个常量,SWIG必须能够完全解析#define中使用的所有符号。这个限制是必要的,因为#define还用于定义预处理器宏,这些宏肯定不是脚本语言接口的一部分。例如:

#define EXTERN extern
EXTERN void foo();

  在这种情况下,您可能不想创建一个名为EXTERN的常量(该值是什么?)通常,除非预处理器可以完全确定该值,否则SWIG不会为宏创建常量。例如,在上面的例子中,声明

#define PI_4  PI/4

  定义一个常数,因为PI已经被定义为一个常数,其值是已知的。然而,出于同样的保守原因,即使是具有简单强制转换的常量也会被忽略,例如

#define F_CONST (double) 5            // A floating point constant with cast

  这种逻辑可能会导致错误的尝试将#define转换为%constant。例如,下面的例子在宏中没有任何未定义的符号:

// For indicating pure virtual functions such as: virtual void f() PURE;
#define PURE = 0

  A warning is issued:

pure.h:1: Warning 305: Bad constant value (ignored).

  在这种情况下,简单地忽略警告或使用正常的警告抑制技术抑制它。

  允许使用常量表达式,但SWIG不计算它们。相反,它将它们传递给输出文件,并让C编译器执行最终的求值(不过,SWIG确实执行了一种有限形式的类型检查)。

   对于枚举,原始枚举定义必须包含在接口文件的某个地方(在头文件中或在%{%}块中)。SWIG只将枚举转换为将常量添加到脚本语言所需的代码。它需要原始的枚举声明,以便获得C编译器赋给的正确枚举值。

   %constant指令用于更精确地创建对应于不同C数据类型的常量。虽然简单值通常不需要它,但在处理指针和其他更复杂的数据类型时,它更有用。通常,只有当您想向脚本语言接口添加原始头文件中没有定义的常量时,才使用%constant。

5.2.4 A brief word about const

   C编程的一个常见混淆是const限定符在声明中的语义含义——特别是当它与指针和其他类型修饰符混合在一起时。事实上,先前的SWIG版本错误地处理了const——SWIG-1.3.7和更新版本已经修复了这种情况。

   从SWIG-1.3开始,所有变量声明(无论使用const)都被包装为全局变量。如果一个声明碰巧被声明为const,那么它就被包装为只读变量。要判断一个变量是否是const,需要查看const限定符最右边的位置(出现在变量名之前)。如果最右边的const出现在所有其他类型修饰符(如指针)之后,则该变量是const。否则,它就不是。

   下面是一些const声明的例子。

const char a;           // A constant character
char const b;           // A constant character (the same)
char *const c;          // A constant pointer to a character
const char *const d;    // A constant pointer to a constant character

  下面是一个非const声明的例子:

const char *e;          // A pointer to a constant character.  The pointer
                        // may be modified.

  在本例中,指针e可以更改——只有所指向的值是只读的。

  请注意,对于函数中使用的const形参或返回类型,SWIG基本上忽略了它们是const的事实,有关更多信息,请参阅常量正确性一节。

  兼容性注意 : 将SWIG改为将const声明作为只读变量处理的一个原因是,在许多情况下,const变量的值可能会改变。例如,库可以在其公共API中将符号导出为const,以阻止修改,但仍然允许通过其他类型的内部机制更改值。此外,程序员经常忽略这样一个事实:使用char *const这样的常量声明,所指向的底层数据可以被修改——只有指针本身是常量。在嵌入式系统中,const声明可能引用只读内存地址,比如内存映射的I/O设备端口的位置(其中值会改变,但硬件不支持向该端口写入)。不再尝试在const限定符中构建一堆特殊情况,将const解释为“只读”的新解释很简单,并且与C/ c++中const的实际语义完全匹配。如果你真的想像旧版本的SWIG一样创建一个常量,可以使用%constant指令。例如

%constant double PI = 3.14159;

  or

#ifdef SWIG
#define const %constant
#endif
const double foo = 3.4;
const double bar = 23.4;
const int    spam = 42;
#ifdef SWIG
#undef const
#endif
...

5.2.5 A cautionary tale of char *

  在进一步讨论之前,有一点关于char *的注意事项现在必须提到。当字符串从脚本语言传递到C char *时,指针通常指向存储在解释器内的字符串数据。修改这些数据几乎总是一个非常糟糕的主意。此外,一些语言可能会显式地禁止它。例如,在Python中,字符串应该是不可变的。如果你违反了这一点,当你在世界上释放你的模块时,你可能会收到大量的愤怒。

  问题的主要来源是可能在适当位置修改字符串数据的函数。一个经典的例子是这样的一个函数:

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

  尽管SWIG肯定会为此生成一个包装器,但它的行为是未定义的。 事实上,它可能会导致你的应用程序崩溃的分割错误或其他内存相关的问题。 这是因为s引用目标语言中的一些内部数据——您不应该接触这些数据。

  底线:除了只读输入值外,不要依赖char *。但是,必须注意,您可以使用类型映射更改SWIG的行为。

5.3 Pointers and complex objects

  大多数C程序操作数组、结构和其他类型的对象。本节讨论这些数据类型的处理。

5.3.1 Simple pointers

  指向基本C数据类型的指针,例如

int *
double ***
char **

  完全得到SWIG的支持。SWIG没有尝试将所指向的数据转换为脚本表示形式,而是简单地将指针本身编码为包含指针的实际值和类型标记的表示形式。因此,上述指针的SWIG表示(在Tcl中)可能如下所示:

_10081012_p_int
_1008e124_ppp_double
_f8ac_pp_char

  NULL指针由字符串“NULL”或用类型信息编码的值0表示。

   SWIG将所有指针视为不透明对象。因此,指针可以由函数返回,并在需要时传递给其他C函数。出于所有实际目的,脚本语言接口的工作方式与在C程序中使用指针的方式完全相同。唯一的区别是没有对指针进行解引用的机制,因为这需要目标语言理解底层对象的内存布局。

   指针值的脚本语言表示永远不应该被直接操纵。即使显示的值看起来像十六进制地址,使用的数字可能与实际的机器地址不同(例如,在小端机器上,数字可能以相反的顺序出现)。此外,SWIG通常不会将指针映射到高级对象,如关联数组或列表(例如,将int *转换为整数列表)。SWIG不这样做有几个原因:

  1、C声明中没有足够的信息将指针正确地映射到更高层次的构造。例如,int *可能确实是一个整数数组,但如果它包含1000万个元素,将其转换为list对象可能是个坏主意。

  2、SWIG不知道与指针关联的底层语义。例如,int *可能根本不是数组——也许它是一个输出值!

  3、通过以一致的方式处理所有指针,SWIG的实现大大简化了,而且不容易出错。

5.3.2 Run time pointer type checking

  通过允许从脚本语言操作指针,扩展模块有效地绕过了C/ c++编译器中的编译时类型检查。为了防止错误,类型签名被编码到所有指针值中,并用于执行运行时类型检查。这种类型检查过程是SWIG不可分割的一部分,在不使用typemaps的情况下不能禁用或修改(将在后面的章节中描述)。

  和C一样,void *匹配任何类型的指针。此外,空指针可以传递给任何希望接收指针的函数。虽然这有可能导致崩溃,但NULL指针有时也用作哨兵值或表示缺失/空值。因此,SWIG将NULL指针检查留给应用程序。

5.3.3 Derived types, structs, and classes

  对于其他所有东西(结构体、类、数组等……)SWIG应用了一个非常简单的规则:

Everything else is a pointer

  换句话说,SWIG通过引用操作其他所有内容。这个模型是有意义的,因为大多数C/ c++程序都大量使用指针,而且SWIG可以使用已经存在的类型检查指针机制来处理指向基本数据类型的指针。

  虽然这听起来很复杂,但其实很简单。假设你有一个这样的接口文件:

%module fileio
FILE *fopen(char *, char *);
int fclose(FILE *);
unsigned fread(void *ptr, unsigned size, unsigned nobj, FILE *);
unsigned fwrite(void *ptr, unsigned size, unsigned nobj, FILE *);
void *malloc(int nbytes);
void free(void *);

  在这个文件中,SWIG不知道file是什么,但由于它被用作指针,所以它是什么实际上并不重要。如果你把这个模块包装到Python中,你可以像你期望的那样使用函数:

# Copy a file 
def filecopy(source, target):
    f1 = fopen(source, "r")
    f2 = fopen(target, "w")
    buffer = malloc(8192)
    nbytes = fread(buffer, 8192, 1, f1)
    while (nbytes > 0):
        fwrite(buffer, 8192, 1, f2)
            nbytes = fread(buffer, 8192, 1, f1)
    free(buffer)

  在本例中,f1、f2和buffer都是包含C指针的不透明对象。它们包含什么值并不重要——我们的程序在没有这些知识的情况下也能很好地工作。

5.3.4 Undefined datatypes

  当SWIG遇到未声明的数据类型时,它自动假设它是一个结构或类。例如,假设以下函数出现在SWIG输入文件中:

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

  SWIG根本不知道"Matrix"是什么。然而,它显然是指向某物的指针,因此SWIG使用其泛型指针处理代码生成包装器。

  与C或c++不同,SWIG实际上并不关心之前是否在接口文件中定义了Matrix。这允许SWIG仅从部分或有限的信息生成接口。在某些情况下,您可能并不关心Matrix到底是什么,只要您能够在脚本语言接口中传递一个不透明的引用。

  需要提到的一个重要细节是,当存在未指定的类型名称时,SWIG很乐意为接口生成包装器。然而,所有未指定的类型在内部都被处理为指向结构或类的指针!例如,考虑以下声明:

void foo(size_t num);

  如果size_t未声明,SWIG会生成期望接收size_t *类型的包装器(这个映射将在稍后介绍)。因此,脚本接口的行为可能会很奇怪。例如:

foo(40);
TypeError: expected a _p_size_t.

  解决这个问题的唯一方法是确保使用typedef正确地声明类型名。

5.3.5 Typedef

  像C一样,可以使用typedef在SWIG中定义新的类型名。例如:

typedef unsigned int size_t;

  出现在SWIG接口中的typedef定义不会传播到生成的包装器代码。因此,它们要么需要在包含的头文件中定义,要么像这样放在declarations部分:

%{
/* Include in the generated wrapper file */
typedef unsigned int size_t;
%}
/* Tell SWIG about it */
typedef unsigned int size_t;

  or

%inline %{
typedef unsigned int size_t;
%}

  在某些情况下,您可以包括其他头文件来收集类型信息。例如:

%module example
%import "sys/types.h"

  在这种情况下,你可以像下面这样运行SWIG:

$ swig -I/usr/include -includeall example.i

  需要注意的是,您的里程在这里差别很大。系统头文件是出了名的复杂,并且可能依赖于各种非标准的C编码扩展(例如,对GCC的特殊指令)。除非您确切地指定正确的包含目录和预处理符号,否则这可能无法正常工作(您将不得不进行试验)。

  SWIG跟踪typedef声明,并使用这些信息进行运行时类型检查。例如,如果你使用上面的typedef并有以下函数声明:

void foo(unsigned int *ptr);

  相应的包装器函数将接受unsigned int *或size_t *类型的参数。

5.4 Other Practicalities

  到目前为止,本章已经介绍了在简单接口中使用SWIG所需要知道的几乎所有内容。然而,一些C程序使用的习惯用法在某种程度上更难映射到脚本语言接口。本节描述其中一些问题。

5.4.1 Passing structures by value

  有时,C函数接受按值传递的结构参数。例如,考虑以下函数:

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()函数现在接受指向Vectors的指针,而不是Vectors。在大多数情况下,这个转换是透明的,所以您可能不会注意到。

5.4.2 Return by value

  通过值返回结构或类数据类型的C函数更难处理。考虑以下函数:

Vector cross_product(Vector v1, Vector v2);

  这个函数希望返回Vector,但SWIG实际上只支持指针。因此,SWIG创建了一个这样的包装器:

Vector *wrap_cross_product(Vector *v1, Vector *v2) {
  Vector x = *v1;
  Vector y = *v2;
  Vector *result;
  result = (Vector *) malloc(sizeof(Vector));
  *(result) = cross(x, y);
  return result;
}

  或者使用-c++选项运行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都分配一个新对象并返回对它的引用。当返回的对象不再使用时,由用户来删除它。显然,如果您不知道隐式内存分配并且没有采取措施释放结果,这将导致内存泄漏。也就是说,应该注意的是,一些语言模块现在可以自动跟踪新创建的对象并为您回收内存。要了解更多细节,请参阅每个语言模块的文档。

5.4.3 Linking to structure variables

  当遇到涉及结构的全局变量或类成员时,SWIG将它们作为指针处理。例如,像这样的全局变量

Vector unit_i;

  获取映射到下面这对set/get函数:

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

  同样,要谨慎行事。以这种方式创建的全局变量将在目标脚本语言中显示为指针。释放或销毁这样的指针将是一个非常糟糕的主意。此外,c++类必须提供一个正确定义的复制构造函数,以便正确地进行赋值操作。

5.4.4 Linking to char *

  当出现char *类型的全局变量时,SWIG使用malloc()或new为新值分配内存。具体来说,如果你有一个这样的变量

char *foo;

  SWIG生成以下代码:

/* C mode */
void foo_set(char *value) {
  if (foo) free(foo);
  foo = (char *) malloc(strlen(value)+1);
  strcpy(foo, value);
}

/* C++ mode.  When -c++ option is used */
void foo_set(char *value) {
  if (foo) delete [] foo;
  foo = new char[strlen(value)+1];
  strcpy(foo, value);
}

  如果这不是您想要的行为,可以考虑使用%immutable指令将变量设置为只读。或者,您可以编写一个简短的辅助函数来完全按照您想要的方式设置值。例如:

%inline %{
  void set_foo(char *value) {
    strncpy(foo, value, 50);
  }
%}

  注意:如果您像这样编写辅助函数,您将不得不将其作为目标脚本语言的函数来调用(它不像一个变量那样工作)。例如,在Python中,你必须写:

>>> set_foo("Hello World")

  char *变量的一个常见错误是链接到像这样声明的变量:

char *VERSION = "1.0";

  在这种情况下,变量将是可读的,但任何更改值的尝试都会导致分割或一般保护故障。这是因为,当当前分配给变量的字符串字面值没有使用malloc()或new分配时,SWIG试图使用free或delete释放旧值。要修复这种行为,您可以将变量标记为只读,编写一个typemap(如第6章所述),或者编写一个特殊的set函数,如下所示。另一种方法是将变量声明为数组:

char VERSION[64] = "1.0";

  当声明了const char *类型的变量时,SWIG仍然会生成用于设置和获取值的函数。但是,默认行为不会释放以前的内容(导致可能的内存泄漏)。事实上,当包装这样一个变量时,你可能会得到这样的警告消息:

example.i:20. Typemap warning. Setting const char * variable may leak memory

  这种行为的原因是,const char *变量通常用于指向字符串字面量。例如:

const char *foo = "Hello World\n";

  因此,在这样的指针上调用free()是一个非常糟糕的主意。另一方面,将指针改为指向其他值是合法的。当设置这种类型的变量时,SWIG分配一个新的字符串(使用malloc或new)并将指针更改为指向新值。但是,重复修改该值将导致内存泄漏,因为旧值不会被释放。

5.4.5 Arrays

  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]);

  与C一样,SWIG不执行数组边界检查。由用户来确保指针指向适当分配的内存区域。

  多维数组被转换为指向一个维度更小的数组的指针。例如:

int [10];         // Maps to int *
int [10][20];     // Maps to int (*)[20]
int [10][20][30]; // Maps to int (*)[20][30]

  需要注意的是,在C类型系统中,多维数组a[][]并不等同于单个指针*a或像**a这样的双指针。相反,使用一个指向数组的指针(如上所示),其中指针的实际值是数组的起始内存位置。强烈建议读者在使用SWIG之前重新阅读C课本上关于数组的部分。

  支持数组变量,但默认为只读。例如:

int   a[100][200];

  在本例中,读取变量'a'返回一个类型为int(*)[200]的指针,该指针指向数组的第一个元素&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];
}
%}

  要动态创建不同大小和形状的数组,在接口中编写一些辅助函数可能会很有用。例如:

// Some array helpers
%inline %{
  /* Create any sort of [size] array */
  int *int_array(int size) {
    return (int *) malloc(size*sizeof(int));
  }
  /* Create a two-dimension array [size][10] */
  int (*int_array_10(int size))[10] {
    return (int (*)[10]) malloc(size*10*sizeof(int));
  }
%}

  char数组作为特殊情况由SWIG处理。在这种情况下,目标语言中的字符串可以存储在数组中。例如,如果你有一个这样的声明,

char pathname[256];

  SWIG生成用于获取和设置值的函数,这些函数相当于以下代码:

char *pathname_get() {
  return pathname;
}
void pathname_set(char *value) {
  strncpy(pathname, value, 256);
}

  在目标语言中,可以像设置普通变量一样设置该值。

5.4.6 Creating read-only variables

  可以使用%immutable指令创建只读变量,如下所示:

// File : interface.i

int a;       // Can read/write
%immutable;
int b, c, d;   // Read only variables
%mutable;
double x, y;  // read/write

  %immutable指令启用只读模式,直到它被%mutable指令显式禁用为止。作为像这样关闭或打开只读模式的一种替代方法,单个声明也可以被标记为不可变。例如:

%immutable x;                   // Make x read-only
...
double x;                       // Read-only (from earlier %immutable directive)
double y;                       // Read-write
...

  %mutable和%immutable指令实际上是%feature指令,定义如下:

#define %immutable   %feature("immutable")
#define %mutable     %feature("immutable", "")

  如果你想让所有包装变量都是只读的(除了一两个),采用这种方法可能更容易:

%immutable;                     // Make all variables read-only
%feature("immutable", "0") x;   // except, make x read/write
...
double x;
double y;
double z;
...

  当声明声明为const时,也会创建只读变量。例如:

const int foo;               /* Read only variable */
char * const version="1.0";  /* Read only variable */

  兼容性说明:只读访问过去由一对指令%readonly和%readwrite控制。尽管这些指令仍然有效,但它们会生成一条警告消息。简单地将指令更改为%immutable;和%可变;取消警告。不要忘记额外的分号!

5.4.7 Renaming and ignoring declarations

5.4.7.1 Simple renaming of specific identifiers

  通常,当C声明被包装到目标语言中时,使用该声明的名称。但是,这可能会与脚本语言中的关键字或现有函数产生冲突。要解决名称冲突,你可以使用%rename指令,如下所示:

// interface.i

%rename(my_print) print;
extern void print(const char *);

%rename(foo) a_really_long_and_annoying_name;
extern int a_really_long_and_annoying_name;

  SWIG仍然调用正确的C函数,但在本例中,函数print()在目标语言中实际上被称为“my_print()”。

  %rename指令的位置是任意的,只要它出现在要重命名的声明之前。一种常见的技术是像这样编写代码来包装头文件:

// interface.i

%rename(my_print) print;
%rename(foo) a_really_long_and_annoying_name;

%include "header.h"

  %rename对将来出现的所有名称应用重命名操作。重命名适用于函数、变量、类和结构名、成员函数和成员数据。例如,如果你有24个c++类,它们都有一个名为' print'的成员函数(在Python中这是一个关键字),你可以通过指定将它们全部重命名为' output':

%rename(output) print; // Rename all `print' functions to `output'

  SWIG通常不会执行任何检查,以查看它包装的函数是否已经在目标脚本语言中定义。但是,如果小心使用名称空间和模块,通常可以避免这些问题。

  在包装C代码时,简单地使用带有%rename的标识符/符号通常就足够了。当包装c++代码时,在使用函数重载、默认参数、命名空间、模板专门化等c++特性时,简单使用带有%rename的简单标识符/符号可能太过受限。如果您正在使用%rename指令和c++,请确保您阅读了SWIG和c++章节,特别是关于方法重载和默认参数的重命名和歧义解析的部分。

  与%rename密切相关的是%ignore指令。%ignore指示SWIG忽略匹配给定标识符的声明。例如:

%ignore print;         // Ignore all declarations named print
%ignore MYMACRO;       // Ignore a macro
...
#define MYMACRO 123
void print(const char *);
...

  任何匹配%ignore的函数、变量等都不会被包装,因此在目标语言中不可用。%ignore的常见用法是有选择地从头文件中删除某些声明,而不必向头文件添加条件编译。然而,应该强调的是,这只适用于简单的声明。如果您需要删除整个有问题的代码部分,那么应该使用SWIG预处理程序。

  兼容性注意:旧版本的SWIG提供了一个特殊的%name指令来重命名声明。例如:

%name(output) extern void print(const char *);

  这个指令仍然被支持,但是已经被弃用了,应该避免使用。%rename指令更强大,更好地支持包装原始头文件信息。

5.4.7.2 Advanced renaming support

  虽然为特定声明编写%rename非常简单,但有时需要将相同的重命名规则应用到SWIG输入中的许多(可能是所有)标识符。例如,可能需要对目标语言中的所有名称应用一些转换,以便更好地遵循其命名约定,比如为所有包装函数添加特定的前缀。对每个函数单独进行重命名是不切实际的,所以如果没有指定要重命名的标识符的名称,SWIG支持对所有声明应用重命名规则:

%rename("myprefix_%s") ""; // print -> myprefix_print

  这也表明%rename的参数不一定是字面值字符串,但可以是类似printf()的格式字符串。在最简单的形式中,“%s”被替换为原始声明的名称,如上所示。然而,这并不总是足够的,SWIG提供了对通常格式字符串语法的扩展,以允许将(SWIG定义的)函数应用到参数。例如,要将所有C函数do_something_long()包装为更像java的doSomethingLong(),你可以像这样使用"lowercamelcase"扩展格式说明符:

%rename("%(lowercamelcase)s") ""; // foo_bar -> fooBar; FooBar -> fooBar

  有些函数可以被参数化,例如“strip”one将提供的前缀从其实参中剥离。前缀是作为格式字符串的一部分指定的,后跟函数名后面的冒号:

%rename("%(strip:[wx])s") ""; // wxHello -> Hello; FooBar -> FooBar

  下表总结了当前定义的所有函数,并给出了应用每个函数的示例。注意,其中一些函数有两个名称,一个更短,一个更有描述性,但这两个函数在其他方面是等价的:

FunctionReturnsExample (in/out)
uppercase or upper Upper case version of the string. Print PRINT
lowercase or lower Lower case version of the string. Print print
title String with first letter capitalized and the rest in lower case. print Print
firstuppercase String with the first letter capitalized and the rest unchanged. printIt PrintIt
firstlowercase String with the first letter in lower case and the rest unchanged. PrintIt printIt
camelcase or ctitle String with capitalized first letter and any letter following an underscore (which are removed in the process) and rest in lower case. print_it PrintIt
lowercamelcase or lctitle String with every letter following an underscore (which is removed in the process) capitalized and rest, including the first letter, in lower case. print_it printIt
undercase or utitle Lower case string with underscores inserted before every upper case letter in the original string and any number not at the end of string. Logically, this is the reverse of camelcase. PrintIt print_it
schemify String with all underscores replaced with dashes, resulting in more Lispers/Schemers-pleasing name. print_it print-it
strip:[prefix] String without the given prefix or the original string if it doesn't start with this prefix. Note that square brackets should be used literally, e.g. %rename("strip:[wx]") wxPrint Print
rstrip:[suffix] String without the given suffix or the original string if it doesn't end with this suffix. Note that square brackets should be used literally, e.g. %rename("rstrip:[Cls]") PrintCls Print
regex:/pattern/subst/ String after (Perl-like) regex substitution operation. This function allows to apply arbitrary regular expressions to the identifier names. The pattern part is a regular expression in Perl syntax (as supported by the Perl Compatible Regular Expressions (PCRE)) library and the subst string can contain back-references of the form \N where N is a digit from 0 to 9, or one of the following escape sequences: \l\L\u\U or \E. The back-references are replaced with the contents of the corresponding capture group while the escape sequences perform the case conversion in the substitution string: \l and \L convert to the lower case, while \u and \U convert to the upper case. The difference between the elements of each pair is that \l and \u change the case of the next character only, while \L and \U do it for all the remaining characters or until \E is encountered. Finally please notice that backslashes need to be escaped in C strings, so in practice "\\" must be used in all these escape sequences. For example, to remove any alphabetic prefix before an underscore and capitalize the remaining part you could use the following directive: %rename("regex:/(\\w+)_(.*)/\\u\\2/") prefix_print Print
command:cmd Output of an external command cmd with the string passed to it as input. Notice that this function is extremely slow compared to all the other ones as it involves spawning a separate process and using it for many declarations is not recommended. The cmd is not enclosed in square brackets but must be terminated with a triple '<' sign, e.g. %rename("command:tr -d aeiou <<<") (nonsensical example removing all vowels) Print Prnt

  上述所有函数中最通用的函数是regex函数(不包括理论上更强大但出于性能考虑通常应该避免使用的计数命令)。下面是一些关于它的用法的例子:

// Strip the wx prefix from all identifiers except those starting with wxEVT
%rename("%(regex:/wx(?!EVT)(.*)/\\1/)s") ""; // wxSomeWidget -> SomeWidget
                                             // wxEVT_PAINT -> wxEVT_PAINT

// Apply a rule for renaming the enum elements to avoid the common prefixes
// which are redundant in C#/Java
%rename("%(regex:/^([A-Z][a-z]+)+_(.*)/\\2/)s", %$isenumitem) ""; // Colour_Red -> Red

// Remove all "Set/Get" prefixes.
%rename("%(regex:/^(Set|Get)(.*)/\\2/)s") ""; // SetValue -> Value
                                              // GetValue -> Value

  和前面一样,上面关于%rename的所有内容也适用于%ignore。事实上,后者只是前者的一种特殊情况,忽略标识符等同于将其重命名为特殊的“$ignore”值。下面是片段

%ignore print;

  and

%rename("$ignore") print;

  是完全等效的,并且可以使用%rename使用前面描述的匹配可能性有选择地忽略多个声明。

5.4.7.3 Limiting global renaming rules

  正如前面部分所解释的,可以重命名单个声明,也可以同时对所有声明应用重命名规则。但在实践中,后者很少是适当的,因为总有一些例外的一般规则。要处理它们,可以使用后续匹配参数限制未命名的%rename的范围。它们可以应用于SWIG与其输入中出现的声明相关联的任何属性。例如:

%rename("foo", match$name="bar") "";

  能用来达到同样的效果是不是更简单

%rename("foo") bar;

  它本身并不是很有趣。不过,match也可以应用于声明类型,例如match="class"限制匹配仅为类声明(在c++中),match="enumitem"限制匹配为enum元素。例如,SWIG还为此类匹配表达式提供了方便的宏

%rename("%(title)s", %$isenumitem) "";

  将所有枚举元素的名称大写,但不改变其他声明的大小写。同样,%$isclass, %$isfunction, %$isconstructor, %$isunion, %$istemplate和%$isvariable也可以使用。还可以进行许多其他检查,本文档并不是详尽的,请参阅swig中的“%rename谓词”一节。SWG提供支持的匹配表达式的完整列表。

  除了用match匹配字符串之外,还可以使用regexmatch或notregexmatch来匹配字符串和正则表达式。例如,忽略所有可以使用“Old”作为后缀的函数

%rename("$ignore", regexmatch$name="Old$") "";

  对于像这样的简单情况,直接为声明名指定正则表达式是更好的,也可以使用regextarget:

%rename("$ignore", regextarget=1) "Old$";

  注意,该检查只针对声明本身的名称进行,如果你需要匹配c++声明的全名,你必须使用fullname属性:

%rename("$ignore", regextarget=1, fullname=1) "NameSpace::ClassName::.*Old$";

  对于notregexmatch,它只将匹配限制为不匹配指定正则表达式的字符串。因此,将所有声明重命名为小写,除了那些只包含大写字母的声明:

%rename("$(lower)s", notregexmatch$name="^[A-Z]+$") "";

  最后,%rename和%ignore指令的变体可以用来帮助包装c++重载函数和方法,或者使用默认实参的c++方法。c++一章的重命名和歧义解决部分对此进行了描述。

5.4.7.4 Ignoring everything then wrapping a few selected symbols

  使用上述技术,可以忽略头文件中的所有内容,然后有选择地包装一些选定的方法或类。例如,考虑一个头文件myheader.h,它有很多类,并且在这个头文件中只需要一个名为Star的类,可以采用以下方法:

%ignore ""; // Ignore everything

// Unignore chosen class 'Star'
%rename("%s") Star;

// As the ignore everything will include the constructor, destructor, methods etc
// in the class, these have to be explicitly unignored too:
%rename("%s") Star::Star;
%rename("%s") Star::~Star;
%rename("%s") Star::shine; // named method

%include "myheader.h"

  另一种方法可能更合适,因为它不需要命名所选类中的所有方法,这是一开始只忽略类。这不会向类的任何成员显式添加忽略,所以当选择的类没有被忽略时,它的所有方法都将被包装。

%rename($ignore, %$isclass) ""; // Only ignore all classes
%rename("%s") Star; // Unignore 'Star'
%include "myheader.h"

5.4.8 Default/optional arguments

  SWIG支持C和c++代码中的默认参数。例如:

int plot(double x, double y, int color=WHITE);

  在这种情况下,SWIG生成包装器代码,其中默认参数在目标语言中是可选的。例如,这个函数可以在Tcl中如下所示:

% plot -3.4 7.5    # Use default value
% plot -3.4 7.5 10 # set color to 10 instead

  虽然ISO C标准不允许默认实参,但SWIG接口中指定的默认实参同时适用于C和c++。

  注意:在使用默认参数和SWIG生成的包装器代码方面存在一个微妙的语义问题。当在C代码中使用默认实参时,默认值被发送到包装器中,并使用一组完整的实参调用函数。这与封装c++时不同,在c++中为每个默认参数生成重载的包装器方法。请参阅c++章节中关于默认参数的章节以获得更多细节。

5.4.9 Pointers to functions and callbacks

  偶尔,C库可能包含期望接收函数指针的函数——可能用作回调函数。当回调函数是在C而不是在目标语言中定义时,SWIG提供了对函数指针的全面支持。例如,考虑这样一个函数:

int binary_op(int a, int b, int (*op)(int, int));

  当您第一次将类似这样的内容包装到扩展模块中时,您可能会发现该函数无法使用。例如,在Python中:

>>> def add(x, y):
...     return x+y
...
>>> binary_op(3, 4, add)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: Type error. Expected _p_f_int_int__int
>>>

  产生此错误的原因是SWIG不知道如何将脚本语言函数映射到C回调函数中。但是,现有的C函数可以作为参数使用,前提是将它们作为常量安装。一种方法是使用%constant指令,就像这样:

/* Function with a callback */
int binary_op(int a, int b, int (*op)(int, int));

/* Some callback functions */
%constant int add(int, int);
%constant int sub(int, int);
%constant int mul(int, int);

  在本例中,add、sub和mul成为目标脚本语言中的函数指针常量。这允许你使用他们如下:

>>> binary_op(3, 4, add)
7
>>> binary_op(3, 4, mul)
12

  不幸的是,通过将回调函数声明为常量,它们将不再作为函数进行访问。例如:

>>> add(3, 4)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: object is not callable: '_ff020efc_p_f_int_int__int'

  如果你想让一个函数既作为回调函数又作为函数可用,你可以像这样使用%callback和%nocallback指令:

/* Function with a callback */
int binary_op(int a, int b, int (*op)(int, int));

/* Some callback functions */
%callback("%s_cb");
int add(int, int);
int sub(int, int);
int mul(int, int);
%nocallback;

  %callback的参数是一个printf风格的格式字符串,它指定了回调常量的命名约定(%s将被函数名替换)。回调模式一直有效,直到使用%nocallback显式禁用为止。当你这样做时,界面现在如下所示:

>>> binary_op(3, 4, add_cb)
7
>>> binary_op(3, 4, mul_cb)
12
>>> add(3, 4)
7
>>> mul(3, 4)
12

  注意,当函数用作回调时,会使用特殊的名称,比如add_cb。要正常调用该函数,只需使用原始函数名,如add()。

  SWIG提供了许多对标准C printf格式的扩展,这些扩展在此上下文中可能很有用。例如,下面的变体将回调安装为所有的大写常量,如ADD、SUB和MUL:

/* Some callback functions */
%callback("%(uppercase)s");
int add(int, int);
int sub(int, int);
int mul(int, int);
%nocallback;

  格式字符串"%(小写)s"将所有字符转换为小写。字符串“%(title)s”将第一个字符大写,并将其余字符转换为小写。

  现在,关于函数指针支持的最后一个注意事项。虽然SWIG通常不允许用目标语言编写回调函数,但这可以通过使用类型映射和其他SWIG高级特性来实现。有关类型映射的更多信息,请参阅Typemaps一章,有关回调的更多信息,请参阅单个目标语言章节。“director”特性可以用于从C/ c++回调到目标语言,参见目标语言的回调。

5.5 Structures and unions

  本节描述SWIG在处理ISO C结构和联合声明时的行为。下一节将描述处理c++的扩展。

  如果SWIG遇到结构或联合的定义,它将创建一组访问器函数。尽管SWIG不需要结构定义来构建接口,但提供定义使访问结构成员成为可能。SWIG生成的访问器函数只是接受一个指向对象的指针,并允许访问单个成员。例如,声明:

struct Vector {
  double x, y, z;
}

  获取转换为以下访问器函数集:

double Vector_x_get(struct Vector *obj) {
  return obj->x;
}
double Vector_y_get(struct Vector *obj) { 
  return obj->y;
}
double Vector_z_get(struct Vector *obj) { 
  return obj->z;
}
void Vector_x_set(struct Vector *obj, double value) {
  obj->x = value;
}
void Vector_y_set(struct Vector *obj, double value) {
  obj->y = value;
}
void Vector_z_set(struct Vector *obj, double value) {
  obj->z = value;
}

  此外,如果在接口中没有定义任何默认构造函数和析构函数,则SWIG将创建它们。例如:

struct Vector *new_Vector() {
  return (Vector *) calloc(1, sizeof(struct Vector));
}
void delete_Vector(struct Vector *obj) {
  free(obj);
}

  使用这些低级访问器函数,可以在目标语言中使用如下代码对对象进行最低限度的操作:

v = new_Vector()
Vector_x_set(v, 2)
Vector_y_set(v, 10)
Vector_z_set(v, -5)
...
delete_Vector(v)

  然而,大多数SWIG的语言模块还提供更方便的高级接口。继续阅读。

5.5.1 Typedef and structures

  SWIG支持以下结构,这在C程序中很常见:

typedef struct {
  double x, y, z;
} Vector;

  遇到时,SWIG假定对象的名称为“Vector”,并像前面一样创建访问器函数。唯一的区别是typedef的使用允许SWIG在其生成的代码上删除struct关键字。例如:

double Vector_x_get(Vector *obj) {
  return obj->x;
}

  如果两个不同的名字是这样使用的:

typedef struct vector_struct {
  double x, y, z;
} Vector;

  名称vector而不是vector_struct,因为这是更典型的C编程风格。如果稍后在接口中定义的声明使用类型struct vector_struct, SWIG知道这与Vector相同,并生成适当的类型检查代码。

5.5.2 Character strings and structures

  包含字符串的结构需要注意。SWIG假定char *类型的所有成员都是使用malloc()动态分配的,并且它们是以null结尾的ASCII字符串。当这样一个成员被修改时,将会发布之前的内容,并分配新的内容。例如:

%module mymodule
...
struct Foo {
  char *name;
  ...
}

  这会产生以下访问器函数:

char *Foo_name_get(Foo *obj) {
  return Foo->name;
}

char *Foo_name_set(Foo *obj, char *c) {
  if (obj->name)
    free(obj->name);
  obj->name = (char *) malloc(strlen(c)+1);
  strcpy(obj->name, c);
  return obj->name;
}

  如果这种行为与您在应用程序中需要的不同,可以使用SWIG“memberin”类型映射来更改它。更多细节请参见typemaps一章。

  注意:如果使用-c++选项,则使用new和delete来执行内存分配。

5.5.3 Array members

  数组可以作为结构的成员出现,但它们将是只读的。SWIG将编写一个访问器函数,该函数返回指向数组第一个元素的指针,但不会编写一个函数来更改数组本身的内容。当检测到这种情况时,SWIG可能会生成如下警告消息:

interface.i:116. Warning. Array member will be read-only

  为了消除警告消息,可以使用typemaps,但这将在后面的章节中讨论。在许多情况下,警告消息是无害的。

5.5.4 Structure data members

  有时,结构将包含本身是结构的数据成员。例如:

typedef struct Foo {
  int x;
} Foo;

typedef struct Bar {
  int y;
  Foo f;           /* struct member */
} Bar;

  当结构成员被包装时,它被当作指针来处理,除非使用%naturalvar指令来处理它更像c++引用(参见c++成员数据)。作为指针的成员变量的访问器被有效地包装如下:

Foo *Bar_f_get(Bar *b) {
  return &b->f;
}
void Bar_f_set(Bar *b, Foo *value) {
  b->f = *value;
}

  其原因有些微妙,但与修改和访问数据成员内部数据的问题有关。例如,假设你想修改Bar对象的f.x值,就像这样:

Bar *b;
b->f.x = 37;

  将这个赋值转换为函数调用(就像在脚本语言接口中使用的那样)会产生以下代码:

Bar *b;
Foo_x_set(Bar_f_get(b), 37);

  在这段代码中,如果Bar_f_get()函数返回Foo而不是Foo *,那么结果修改将应用于f的副本,而不是数据成员f本身。很明显这不是你想要的!

应该注意的是,只有当SWIG知道数据成员是结构或类时,才会发生这种到指针的转换。例如,如果你有这样一个结构,

struct Foo {
  WORD   w;
};

  并且对WORD一无所知,那么SWIG将生成更多像这样的普通访问器函数:

WORD Foo_w_get(Foo *f) {
  return f->w;
}
void Foo_w_set(FOO *f, WORD value) {
  f->w = value;
}

  兼容性注意:SWIG-1.3.11和更早的版本将所有非基元成员数据类型转换为指针。从SWIG-1.3.12开始,这种转换只发生在已知数据类型为结构、类或联合的情况下。这不太可能破坏现有的代码。然而,如果您需要告诉SWIG一个未声明的数据类型实际上是一个结构体,只需使用一个forward结构体声明,比如“struct Foo;”。

5.5.5 C constructors and destructors

  在包装结构时,拥有创建和销毁对象的机制通常是有用的。如果您什么都不做,SWIG将自动生成函数,用于使用malloc()和free()创建和销毁对象。注意:malloc()的使用仅适用于在C代码中使用SWIG时(即,当命令行上没有提供-c++选项时)。c++的处理方式不同。

  如果不想让SWIG为接口生成默认构造函数,可以使用% nodefaulttor指令或- nodefaulttor命令行选项。例如:

swig -nodefaultctor example.i 

  or

%module foo
...
%nodefaultctor;        // Don't create default constructors
... declarations ...
%clearnodefaultctor;   // Re-enable default constructors

  如果需要更精确的控制,% nodefaulttor可以有选择地针对各个结构定义。例如:

%nodefaultctor Foo;      // No default constructor for Foo
...
struct Foo {             // No default constructor generated.
};

struct Bar {             // Default constructor generated.
};

  由于忽略隐式析构函数或默认析构函数在大多数情况下都会产生内存泄漏,因此SWIG总是尝试生成它们。但是,如果需要,您可以使用% nodefaulttor有选择地禁用默认/隐式析构函数的生成

%nodefaultdtor Foo; // No default/implicit destructor for Foo
...
struct Foo {              // No default destructor is generated.
};

struct Bar {              // Default destructor generated.
};

  兼容性注意:在SWIG-1.3.7之前,除非使用-make_default显式地打开默认构造函数或析构函数,否则SWIG不会生成默认构造函数或析构函数。然而,似乎大多数用户都希望拥有构造函数和析构函数,因此它现在已被启用为默认行为。

  注意:还有-nodefault选项和%nodefault指令,它们禁用默认或隐式析构函数生成。这可能会导致目标语言中的内存泄漏,强烈建议不要使用它们。

5.5.6 Adding member functions to C structures

  大多数语言都提供了创建类和支持面向对象编程的机制。从C的角度来看,面向对象编程实际上只是将函数附加到结构的过程。这些函数通常对结构(或对象)的实例进行操作。尽管c++与这种方案有一种自然的映射,但没有直接的机制在C代码中使用它。然而,SWIG提供了一个特殊的%extend指令,它可以将方法附加到C结构中,以构建面向对象的接口。假设您有一个带有以下声明的C头文件

/* file : vector.h */
...
typedef struct Vector {
  double x, y, z;
} Vector;

  你可以通过编写如下SWIG接口来让Vector看起来更像一个类:

// file : vector.i
%module mymodule
%{
#include "vector.h"
%}

%include "vector.h"          // Just grab original C header file
%extend Vector {             // Attach these functions to struct Vector
  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;
  }
  ~Vector() {
    free($self);
  }
  double magnitude() {
    return sqrt($self->x*$self->x+$self->y*$self->y+$self->z*$self->z);
  }
  void print() {
    printf("Vector [%g, %g, %g]\n", $self->x, $self->y, $self->z);
  }
};

  注意$self特殊变量的用法。它的用法与c++的'this'指针相同,应该在需要访问struct实例时使用。还要注意的是,c++的构造函数和析构函数语法已经用于模拟构造函数和析构函数,甚至对于C代码也是如此。不过,与普通c++构造函数实现有一个细微的区别,那就是,尽管构造函数声明与普通c++构造函数一样,但必须返回新构造的对象,就像构造函数声明有一个返回值一样,在本例中是Vector *。

  现在,当在Python中与代理类一起使用时,你可以这样做:

>>> v = Vector(3, 4, 0)                 # Create a new vector
>>> print v.magnitude()                # Print magnitude
5.0
>>> v.print()                  # Print it out
[ 3, 4, 0 ]
>>> del v                      # Destroy it

  %extend指令也可以在Vector结构的定义中使用。例如:

// file : vector.i
%module mymodule
%{
#include "vector.h"
%}

typedef struct Vector {
  double x, y, z;
  %extend {
    Vector(double x, double y, double z) { ... }
    ~Vector() { ... }
    ...
  }
} Vector;

  注意,%extend可以用来访问外部编写的函数,前提是它们遵循本例中使用的命名约定:

/* File : vector.c */
/* Vector methods */
#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);
}

double Vector_magnitude(Vector *v) {
  return sqrt(v->x*v->x+v->y*v->y+v->z*v->z);
}

// File : vector.i
// Interface file
%module mymodule
%{
#include "vector.h"
%}

typedef struct Vector {
  double x, y, z;
  %extend {
    Vector(int, int, int); // This calls new_Vector()
    ~Vector();           // This calls delete_Vector()
    double magnitude();  // This will call Vector_magnitude()
    ...
  }
} Vector;

  %extend使用的名称应该是struct的名称,而不是struct的任何类型定义的名称。例如:

typedef struct Integer {
  int value;
} Int;
%extend Integer { ...  } /* Correct name */
%extend Int { ...  } /* Incorrect name */

struct Float {
  float value;
};
typedef struct Float FloatValue;
%extend Float { ...  } /* Correct name */
%extend FloatValue { ...  } /* Incorrect name */

  该规则有一个例外,那就是struct是匿名命名的,比如:

typedef struct {
  double value;
} Double;
%extend Double { ...  } /* Okay */

  %extend指令的一个鲜为人知的特性是,它还可以用来添加合成属性或修改现有数据属性的行为。例如,假设您希望将量级作为Vector的只读属性,而不是方法。要做到这一点,你可以写一些像这样的代码:

// Add a new attribute to Vector
%extend Vector {
  const double magnitude;
}
// Now supply the implementation of the Vector_magnitude_get function
%{
const double Vector_magnitude_get(Vector *v) {
  return (const double) sqrt(v->x*v->x+v->y*v->y+v->z*v->z);
}
%}

  现在,为了所有的实际目的,大小将看起来像一个物体的属性。

  还可以使用类似的技术处理希望处理的数据成员。例如,考虑以下接口:

typedef struct Person {
  char name[50];
  ...
} Person;

  假设你想确保name总是大写的,你可以重写接口如下,以确保无论何时读取或写入一个名称,都会发生这种情况:

typedef struct Person {
  %extend {
    char name[50];
  }
  ...
} Person;

%{
#include <string.h>
#include <ctype.h>

void make_upper(char *name) {
  char *c;
  for (c = name; *c; ++c)
    *c = (char)toupper((int)*c);
}

/* Specific implementation of set/get functions forcing capitalization */

char *Person_name_get(Person *p) {
  make_upper(p->name);
  return p->name;
}

void Person_name_set(Person *p, char *val) {
  strncpy(p->name, val, 50);
  make_upper(p->name);
}
%}

  最后,应该强调的是,即使%extend可以用于添加新的数据成员,但这些新成员不能要求在对象中分配额外的存储空间(例如,它们的值必须完全由结构的现有属性合成或从其他地方获得)。

  兼容性注意:%extend指令是%addmethods指令的新名称。由于%addmethods可以用于扩展一个结构,而不仅仅是方法,所以我们选择了一个更合适的指令名。

5.5.7 Nested structures

  偶尔,C程序会涉及这样的结构:

typedef struct Object {
  int objtype;
  union {
    int ivalue;
    double dvalue;
    char *strvalue;
    void *ptrvalue;
  } intRep;
} Object;

  当SWIG遇到这种情况时,它执行一个结构拆分操作,将声明转换为如下所示:

typedef union {
  int ivalue;
  double dvalue;
  char *strvalue;
  void *ptrvalue;
} Object_intRep;

typedef struct Object {
  int objType;
  Object_intRep intRep;
} Object;

  然后,SWIG将创建一个Object_intRep结构,以便在接口文件中使用。将为这两个结构创建访问器函数。在这种情况下,会创建这样的函数:

Object_intRep *Object_intRep_get(Object *o) {
  return (Object_intRep *) &o->intRep;
}
int Object_intRep_ivalue_get(Object_intRep *o) {
  return o->ivalue;
}
int Object_intRep_ivalue_set(Object_intRep *o, int value) {
  return (o->ivalue = value);
}
double Object_intRep_dvalue_get(Object_intRep *o) {
  return o->dvalue;
}
... etc ...

  尽管这个过程有点麻烦,但它的工作方式与您在目标脚本语言中所期望的一样——尤其是在使用代理类时。例如,在Perl中:

# Perl5 script for accessing nested member
$o = CreateObject();                    # Create an object somehow
$o->{intRep}->{ivalue} = 7              # Change value of o.intRep.ivalue

  如果有很多嵌套结构声明,最好在运行SWIG后对它们进行双重检查。尽管它们很有可能会工作,但是在某些情况下您可能不得不修改接口文件。

  最后,请注意在c++模式中嵌套的处理是不同的,请参阅嵌套类。

5.5.8 Other things to note about structure wrapping

  SWIG不关心 .i 文件中结构的声明是否与底层C代码中使用的完全匹配(嵌套结构的情况除外)。因此,忽略有问题的成员或简单地忽略整个结构定义是没有问题的。如果您乐于传递指针,那么无需给SWIG提供结构定义就可以实现这一点。

  从SWIG1.3开始,对SWIG的代码生成器进行了许多改进。具体地说,即使结构访问已经在高级访问器函数中进行了描述,

double Vector_x_get(Vector *v) {
  return v->x;
}

  生成的大部分代码实际上都直接内联到包装器函数中。因此,生成的包装器文件中实际上不存在Vector_x_get()函数。例如,在创建Tcl模块时,会生成以下函数:

static int
_wrap_Vector_x_get(ClientData clientData, Tcl_Interp *interp, 
                   int objc, Tcl_Obj *CONST objv[]) {
  struct Vector *arg1 ;
  double result ;

  if (SWIG_GetArgs(interp, objc, objv, "p:Vector_x_get self ", &arg0,
                   SWIGTYPE_p_Vector) == TCL_ERROR)
    return TCL_ERROR;
  result = (double ) (arg1->x);
  Tcl_SetObjResult(interp, Tcl_NewDoubleObj((double) result));
  return TCL_OK;
}

  该规则的唯一例外是使用%extend定义的方法。在这种情况下,添加的代码包含在一个单独的函数中。  

  最后,需要注意的是,大多数语言模块可能会选择构建更高级的接口。尽管您可能永远不会使用这里描述的底层接口,但大多数SWIG的语言模块都以某种方式使用它。

5.6 Code Insertion

  有时需要将特殊的代码插入到由SWIG生成的结果包装器文件中。例如,您可能希望包含额外的C代码来执行初始化或其他操作。有四种常见的插入代码的方法,但是首先知道SWIG的输出是如何结构的是很有用的。

5.6.1 The output of SWIG

  当SWIG创建其输出C/ c++文件时,它被分成五个部分,分别对应于运行时代码、头文件、包装器函数和模块初始化代码(按此顺序)。

  • Begin section.

    一个占位符,供用户将代码放在C/ c++包装文件的开头。这通常用于定义后面部分中使用的预处理器宏。

  • Runtime code.

    这段代码是SWIG的内部代码,用于包含类型检查和其他支持功能,这些功能将由模块的其余部分使用。

  • Header section.

    这是用户定义的支持代码,包含在%{…%}指令。通常这由头文件和其他帮助函数组成。

  • Wrapper code.

    这些是SWIG自动生成的包装器。

  • Module initialization.

    SWIG生成的函数,用于在加载时初始化模块。

5.6.2 Code insertion blocks

  %insert指令允许将代码块插入生成代码的给定部分。它可以用在两种方式之一:

%insert("section") "filename"
%insert("section") %{ ... %}

  第一个将把给定文件名中的文件内容转储到指定的部分中。第二个将括号中的代码插入到指定的部分中。例如,下面的代码将代码添加到运行时部分:

%insert("runtime") %{
  ... code in runtime section ...
%}

  有5个部分,然而,一些目标语言添加了额外的部分,其中一些导致代码生成到目标语言文件中,而不是C/ c++包装文件中。这些在目标语言章节中都有记录。以代码段命名的宏可以作为附加指令使用,这些宏指令通常用来代替%insert。例如,使用%runtime而不是%insert("runtime")。在生成的C/ c++包装器文件中,有效的节和节的顺序如下所示:

%begin %{
  ... code in begin section ...
%}

%runtime %{
  ... code in runtime section ...
%}

%header %{
  ... code in header section ...
%}

%wrapper %{
  ... code in wrapper section ...
%}

%init %{
  ... code in init section ...
%}

  裸露的%{…%}指令是一个快捷方式,与%header %{…%}。

  %begin部分实际上是空的,因为它默认只包含SWIG标题。提供此部分的目的是让用户在生成任何其他代码之前在包装器文件的顶部插入代码。代码插入块中的所有内容都被逐字复制到输出文件中,并且不被SWIG解析。大多数SWIG输入文件至少有一个这样的块来包含头文件并支持C代码。其他代码块可以根据需要放置在SWIG文件的任何位置。

%module mymodule
%{
#include "my_header.h"
%}
... Declare functions here
%{

void some_extra_function() {
  ...
}
%}

  代码块的一个常见用途是编写“助手”函数。这些函数是专门用于构建接口的,但通常对普通C程序是不可见的。例如:

%{
/* Create a new vector */
static Vector *new_Vector() {
  return (Vector *) malloc(sizeof(Vector));
}

%}
// Now wrap it 
Vector *new_Vector();

5.6.3 Inlined code blocks

  由于编写辅助函数的过程相当普遍,有一种特殊的代码块内联形式,使用如下:

%inline %{
/* Create a new vector */
Vector *new_Vector() {
  return (Vector *) malloc(sizeof(Vector));
}
%}

  这和写成如下是一样的:

%{
/* Create a new vector */
Vector *new_Vector() {
  return (Vector *) malloc(sizeof(Vector));
}
%}

/* Create a new vector */
Vector *new_Vector() {
  return (Vector *) malloc(sizeof(Vector));
}

  换句话说,%inline指令将后面的所有代码逐字插入接口文件的头部分。然后由SWIG预处理器和解析器解析代码。因此,上面的示例只使用一个声明创建了一个新的命令new_Vector。因为代码在%inline %{…%}块同时给了C编译器和SWIG,在%{…%}。

  注意:当SWIG解析此代码时,通常的SWIG C预处理器规则适用于%apply块中的代码。例如,如前所述,SWIG的C预处理器在默认情况下不遵循#include指令。

5.6.4 Initialization blocks

  当代码包含在%init部分时,它会被直接复制到模块初始化函数中。例如,如果你需要在模块加载时执行一些额外的初始化,你可以这样写:

%init %{
  init_variables();
%}

  请注意,一些语言后端(例如c#或Java)没有任何初始化函数,因此你应该定义一个全局对象来为它们执行必要的初始化:

%init %{
  static struct MyInit { MyInit() { init_variables(); } } myInit;
%}

5.7 An Interface Building Strategy

  本节描述使用SWIG构建接口的一般方法。与特定脚本语言相关的细节可以在后面的章节中找到。

5.7.1 Preparing a C program for SWIG

  SWIG不需要对C代码进行修改,但是如果向它提供一组原始的C头文件或源代码,结果可能不是您所期望的——事实上,结果可能很糟糕。下面是为C程序创建接口的一系列步骤:

  • 确定要包装的函数。可能没有必要访问C程序的每一个函数——因此,稍加考虑就可以极大地简化生成的脚本语言接口。C头文件是查找要包装的东西的一个特别好的来源。
  • 创建一个新的接口文件来描述程序的脚本语言接口。
  • 将适当的声明复制到接口文件中,或者使用SWIG的%include指令来处理整个C源文件/头文件。
  • 确保接口文件中的所有内容都使用ISO C/ c++语法。
  • 确保所有必要的' typedef'声明和类型信息在接口文件中可用。特别是,要确保类型信息按照C/ c++编译器要求的正确顺序指定。
  • 最重要的是,在使用类型之前定义它!C编译器将告诉您是否需要完整的类型信息,而SWIG通常不会发出警告或出错,因为它的设计是在没有完整类型信息的情况下工作。然而,如果类型信息没有正确指定,包装器可能会是次优的,甚至导致无法编译的C/ c++代码。
  • 如果你的程序有一个main()函数,你可能需要重命名它(继续读)。
  • 运行SWIG并编译。

  虽然这听起来很复杂,但一旦你掌握了窍门,这个过程就会变得相当简单。

  在构建接口的过程中,SWIG可能会遇到语法错误或其他问题。处理这个问题的最好方法是简单地将出错的代码复制到一个单独的接口文件中并进行编辑。但是,SWIG开发人员已经非常努力地改进了SWIG解析器——您应该向SWIG -devel邮件列表或SWIG bug跟踪器报告解析错误。

5.7.2 The SWIG interface file

  使用SWIG的首选方法是生成一个单独的接口文件。假设你有以下C头文件:

/* File : header.h */

#include <stdio.h>
#include <math.h>

extern int foo(double);
extern double bar(int, int);
extern void dump(FILE *f);

  这个头文件的典型SWIG接口文件应该如下所示:

/* File : interface.i */
%module mymodule
%{
#include "header.h"
%}
extern int foo(double);
extern double bar(int, int);
extern void dump(FILE *f);

  当然,在这种情况下,我们的头文件非常简单,所以我们可以使用更简单的方法,使用像这样的接口文件:

/* File : interface.i */
%module mymodule
%{
#include "header.h"
%}
%include "header.h"

  这种方法的主要优点是,当头文件将来发生更改时,只需对接口文件进行最少的维护。在更复杂的项目中,包含大量%include和#include语句的接口文件是设计接口文件最常用的方法之一,因为维护开销更低。

5.7.3 Why use separate interface files?

  虽然SWIG可以解析许多头文件,但更常见的是编写一个特殊的.i文件来定义包的接口。你可能想这么做的原因有几个:

  • 很少需要访问一个大包中的每个函数。许多C函数在脚本环境中可能很少或根本没用。因此,为什么要包装它们呢?
  • 单独的接口文件提供了一个机会,可以提供关于如何构造接口的更精确的规则。
  • 接口文件可以提供更多的结构和组织。
  • SWIG不能解析头文件中出现的某些定义。拥有一个单独的文件可以让您消除或解决这些问题。
  • 接口文件提供了更精确的接口定义。想要扩展系统的用户可以访问接口文件,并立即查看可用的内容,而不需要从头文件中挖掘。

5.7.4 Getting the right header files

  有时,为了正确编译SWIG生成的代码,必须使用某些头文件。确保你通过使用%{%}块包括某些头文件,像这样:

%module graphics
%{
#include <GL/gl.h>
#include <GL/glu.h>
%}

// Put the rest of the declarations here
...

5.7.5 What to do with main()

  如果你的程序定义了一个main()函数,你可能需要去掉它或者重命名它以便使用脚本语言。大多数脚本语言都定义了自己的main()过程。Main()在使用动态加载时也没有意义。有几种方法可以解决main()冲突:

  • Get rid of main() entirely.
  • 将main()重命名为其他内容。可以通过使用-Dmain=oldmain这样的选项编译C程序来实现这一点。
  • 当不使用脚本语言时,使用条件编译只包含main()。

  Get rid of main()可能会导致程序潜在的初始化问题。为了处理这个问题,您可以考虑编写一个名为program_init()的特殊函数,它在启动时初始化您的程序。然后,该函数可以作为第一个操作从脚本语言中调用,也可以在加载SWIG生成的模块时调用。

  一般来说,许多C程序只使用main()函数来解析命令行选项和设置参数。然而,通过使用脚本语言,您可能试图创建一个更具交互性的程序。在许多情况下,旧的main()程序可以完全替换为Perl、Python或Tcl脚本。

  注意:在某些情况下,您可能倾向于为main()创建脚本语言包装器。如果这样做,编译可能会工作,您的模块甚至可能会正确加载。唯一的问题是,当您调用main()包装器时,您会发现它实际上调用了脚本语言解释器本身的main() !这种行为是动态链接器中使用的符号绑定机制的副作用。底线是:不要这样做。

 

posted @ 2021-11-29 22:29  神龙逗勇士  阅读(151)  评论(0编辑  收藏  举报