四、SWIG 之 预处理与参数处理

一、预处理

1.1 文件包含(%include)

请使用 %include 指令将另一个文件包含到 SWIG 接口文件中,如下所示:

%include "pointer.i"

与 #include 不同,对于每个文件 %include 仅包含一次(并且不会在随后的 %include 声明中重新加载文件)。因此,没有必要在 SWIG 接口文件中使用包含保护。

%module my_module

%{
#include "MyHeader.h"
%}

%include "pointer.i"

这些东西%{ ... %}直接传递给输出; 它本身不是由SWIG解释的.因此,#include确保生成的C/C++代码包含该标头。
%include相比之下,是一个SWIG指令.它告诉SWIG在继续之前处理该头文件。这样,SWIG将了解(并生成包装)该头文件中声明的类型和函数。
 如果标头非常复杂,它可能会混淆SWIG或导致非常大的输出(因为SWIG尝试为其中的所有内容生成包装器).在这种情况下,最好手动声明需要SWIG处理的标题部分,并省略%include.但是你仍然可能想要#include生成的C++来编译。
个人某种理解就是 #include导入的就是自己写的函数或者类所需要的自己代码写的.h。 %include导入的就是内置的 .i 文件(也有可能是 .h 文件)

1.2 文件导入(%import)

%import "foo.i"

    %import 的目的是从另一个 SWIG 接口文件或头文件中收集某些信息,而无需实际生成任何包装代码。此类信息通常包括类型声明(例如 typedef)以及可用作接口中类声明的 C++ 基类。当使用 SWIG 生成扩展作为相关模块的集合时,使用 %import 也很重要。

1.3 条件编译

SWIG 完全支持使用 #if、#ifdef、#ifndef、#else 和 #endif 来有条件地包含接口的各个部分。

/* example.i */
%module example
    
%inline %{
    void foo()
    {
    #ifdef x
        printf("x = %d\n", x);
    #else
        printf("x = %d\n", 10);
    #endif
    }  
%}
# script.py

import example
example.foo()  # x = 10

 

// SWIG 在解析接口时预定义了以下符号:
SWIG                            Always defined when SWIG is processing a file
SWIGIMPORTED                    Defined when SWIG is importing a file with %import
SWIG_VERSION                    Hexadecimal (binary-coded decimal) number containing SWIG version, such as 0x010311 (corresponding to SWIG-1.3.11).
SWIGALLEGROCL                   Defined when using Allegro CL
SWIGCFFI                        Defined when using CFFI
SWIGCHICKEN                     Defined when using CHICKEN
SWIGCLISP                       Defined when using CLISP
SWIGCSHARP                      Defined when using C#
SWIGGUILE                       Defined when using Guile
SWIGJAVA                        Defined when using Java
SWIGJAVASCRIPT                  Defined when using Javascript
SWIG_JAVASCRIPT_JSC             Defined when using Javascript for JavascriptCore
SWIG_JAVASCRIPT_V8              Defined when using Javascript for v8 or node.js
SWIGLUA                         Defined when using Lua
SWIGMODULA3                     Defined when using Modula-3
SWIGMZSCHEME                    Defined when using Mzscheme
SWIGOCAML                       Defined when using Ocaml
SWIGOCTAVE                      Defined when using Octave
SWIGPERL                        Defined when using Perl
SWIGPHP                         Defined when using PHP5 or PHP7
SWIGPHP5                        Defined when using PHP5
SWIGPHP7                        Defined when using PHP7
SWIGPIKE                        Defined when using Pike
SWIGPYTHON                      Defined when using Python
SWIGR                           Defined when using R
SWIGRUBY                        Defined when using Ruby
SWIGSCILAB                      Defined when using Scilab
SWIGSEXP                        Defined when using S-expressions
SWIGTCL                         Defined when using Tcl
SWIGXML                         Defined when using XML

此外,SWIG 定义了以下标准 C/C++ 宏集合:

__LINE__                        Current line number
__FILE__                        Current file name
__STDC__                        Defined to indicate ANSI C
__cplusplus                     Defined when -c++ option used

接口文件可以根据需要查看这些符号,以更改生成接口的方式,或将 SWIG 指令与 C 代码混合。这些符号也在 SWIG 生成的 C 代码中定义(符号 SWIG 仅在 SWIG 编译器中定义)。

1.4 宏扩展

传统的预处理器宏可以在 SWIG 接口中使用。注意,#define 语句也用于尝试和检测常量。因此,如果文件中包含类似的内容,

#ifndef _FOO_H 1
#define _FOO_H 1
#endif

可能会在脚本接口中看到一些额外的常量,例如 _FOO_H 。

可以用标准方式定义更复杂的宏。例如:

#define EXTERN extern
#ifdef __STDC__
#define _ANSI(args)   (args)
#else
#define _ANSI(args) ()
#endif
// 以下运算符可以出现在宏定义中:
#x      // 将宏参数 x 转换为双引号("x")包围的字符串。
x ## y  // 将 x 和 y 串联在一起形成 xy。
x       // 如果 x 是用双引号引起来的字符串,则不执行任何操作。否则,将其转换为类似于 #x 的字符串。这是非标准的 SWIG 扩展。

1.5 SWIG 宏

SWIG 通过 %define 和 %enddef 指令提供增强的宏功能。例如:

/* example.i */
%module example
    
%define ARRAYHELPER(type, name)
%inline %{
type *new_ ## name (int nitems) {
      return (type *) malloc(sizeof(type)*nitems);
}
void delete_ ## name(type *t) {
      free(t);
}
type name ## _get(type *t, int index) {
      return t[index];
}
void name ## _set(type *t, int index, type val) {
      t[index] = val;
}
%}
%enddef
 
ARRAYHELPER(int, IntArray)
ARRAYHELPER(double, DoubleArray)
# script.py

import example

array = example.new_IntArray(10)
example.IntArray_set(array, 0, 10)
example.IntArray_set(array, 1, 20)
example.IntArray_set(array, 2, 30)
print(example.IntArray_get(array, 0))  # 10
print(example.IntArray_get(array, 1))  # 20
print(example.IntArray_get(array, 2))  # 30

%define 的主要目的是定义大型的代码宏。与普通的 C 预处理器宏不同,它不必以连续字符(\)结束每一行——宏定义扩展到第一次出现 %enddef 的地方。此外,扩展此类宏时,将通过 C 预处理器对其进行重新解析。因此,除了嵌套的 %define 语句以外,SWIG 宏可以包含的所有其他预处理器指令。

SWIG 宏功能是一种非常快速简便地生成大量代码的方法。实际上,SWIG 的许多高级功能和库都是使用此机制构建的(例如对 C++ 模板支持)。

1.6 预处理与分隔符

预处理器以不同的方式处理 {...}、"..." 和 %{...%} 分隔符。

1.6.1 预处理与 %{...%}"..." 分隔符

SWIG 预处理器不处理代码块 %{...%} 中包含的任何文本。因此,如果你编写这样的代码,

%{
#ifdef NEED_BLAH
int blah() {
  ...
}
#endif
%}

%{...%} 块的内容被复制并输出(包括所有预处理指令)而不做修改。某种意义上就是将内容进行声明的意思,有点像是 .h 文件

1.6.2 预处理与 {...} 分隔符

SWIG 始终对出现在 {...} 中的文本运行预处理器。但是,有时希望使预处理器指令传递到输出文件。例如:

// example.h

typedef struct {
    int x;
} Foo;

#ifndef DEBUG
#define DEBUG
#endif
%extend Foo {
  void bar() {
    #ifdef DEBUG
      printf("I'm in bar\n");
    #endif
  }
}

# script.py

import example

foo = example.Foo()
foo.bar()  # I'm in bar

默认情况下,SWIG 将解释 #ifdef DEBUG 语句。但是,如果真的希望该代码真正进入包装文件,请在预处理器指令前加上 % 前缀,如下所示:

/* example.i */
%module example

%{
#include "example.h"
%}
 
%include "example.h" 

%extend Foo {
  void bar() {
    %#ifdef DEBUG
      printf("I'm in bar\n");
    %#endif
  }
}

SWIG 将剥离多余的 % 并将预处理器指令保留在代码中。

# script.py

import example

foo = example.Foo()
foo.bar()  # I'm in bar

1.7 预处理器与类型映射(不知道有什么用)

类型映射支持名为 noblock 的特殊属性,其中可以使用 {...} 分隔符,但实际上不会将分隔符生成为代码。这样,其效果类似于使用 "..." 或 %{...%} 分隔符,但是代码通过预处理器运行。例如:

#define SWIG_macro(CAST) (CAST)$input
%typemap(in) Int {$1= SWIG_macro(int);}

也许会生成

{
    arg1=(int)jarg1;
}

然而

#define SWIG_macro(CAST) (CAST)$input
%typemap(in, noblock=1) Int {$1= SWIG_macro(int);}

也许会生成

arg1=(int)jarg1;

以及

#define SWIG_macro(CAST) (CAST)$input
%typemap(in) Int %{$1=SWIG_macro(int);%}

也许会生成

arg1=SWIG_macro(int);

1.8 查看预处理器的输出

像许多编译器一样,SWIG 支持 -E 命令行选项来显示预处理器的输出。使用 -E 开关时,SWIG 将不会生成任何包装。而是显示预处理器运行后的结果。这可能有助于调试和查看宏扩展的结果。

1.9 #error 与 #warning 指令

SWIG 支持常用的 #warning 和 #error 预处理器指令。#warning 指令将使 SWIG 发出警告,然后继续处理。#error 指令将导致 SWIG 退出并出现致命错误。用法示例:

#error "This is a fatal error message"
#warning "This is a warning message"

如果使用了 -cpperraswarn 命令行选项,则可以使 #error 行为像 #warning 一样工作。另外,也可以使用 #pragma 指令达到相同的效果,例如:

/* Modified behaviour: #error does not cause SWIG to exit with error */
#pragma SWIG cpperraswarn=1
/* Normal behaviour: #error does cause SWIG to exit with error */
#pragma SWIG cpperraswarn=0

二、参数处理

2.1 typemaps.i 库

2.1.1 引言

有如下 C 函数:

void add(double a, double b, double *result) {
    *result = a + b;
}

明显该函数将值存储在 double *result 参数中。但是,由于 SWIG 不检查函数主体,因此没有办法知道函数的底层行为。

一种解决方法是使用 typemaps.i 库文件,并编写如下接口代码:

// Simple example using typemaps
%module example
%include "typemaps.i"
 
%apply double *OUTPUT { double *result };  
// %apply double *OUTPUT { double * };好像也可以
%inline %{
extern void add(double a, double b, double *result);
%}

%apply 指令告诉 SWIG 将要对某类型应用特殊的类型处理规则。double *OUTPUT 规范是一个规则的名称,OUTPUT可以理解为内置名称,不能随便使用。该规则定义了如何从类型为 double * 的参数返回输出值。该规则将应用于大括号中列出的所有数据类型,在这个例子中为 double *result。

# script.py

import example

foo = example.add(3, 4)
print(foo)  # 7

在这个例子中,可以看到通常在第三个参数中返回的输出值如何神奇地转换为函数返回值。显然,这使函数更易于使用,因为不再需要制造特殊的 double * 对象,并将其以某种方式传递给函数。

一旦将类型映射应用于类型后,它对于以后所有出现的类型和名称都保持有效。例如,可以编写以下内容:

// example.c

void add(double a, double b, double *result) {
    *result = a + b;
}

void sub(double a, double b, double *result) {
    *result = a - b;
}

void mul(double a, double b, double *result) {
    *result = a * b;
}
/* example.i */
%module example

%include "typemaps.i"

%apply double *OUTPUT { double * };

%inline %{
extern void add(double a, double b, double *result);
extern void sub(double a, double b, double *result);
extern void mul(double a, double b, double *result);
%}
# script.py

import example

print(example.add(4, 2))  # 6.0 
print(example.sub(4, 2))  # 2.0 
print(example.mul(4, 2))  # 8.0 

类型映射转换甚至可以扩展为多个返回值

// example.c

void getwinsize(int winid, int *width, int *height) {
    *width = winid / 4;
    *height = winid / 6;
}
/* example.i */
%module example

%include "typemaps.i"

%apply int *OUTPUT { int *width, int *height };

%inline %{
extern void getwinsize(int winid, int *width, int *height);
%}
# script.py

import example

width, height = example.getwinsize(12)
print(width, height)   # 3 2

尽管已经使用 %apply 指令将类型映射规则与数据类型相关联,但是你也可以直接在参数中使用规则名称。例如,可以这样编写接口文件:

// example.c

void add(double a, double b, double *OUTPUT) {
    *OUTPUT = a + b;
}
/* example.i */
%module example

%include "typemaps.i"

%{
extern void add(double a, double b, double *OUTPUT);
%}
extern void add(double a, double b, double *OUTPUT);
# script.py

import example
print(example.add(3, 4))  # 7.0

类型映射将一直有效,直到将其明确删除或重新定义为其他类型为止。要清除类型映射,应使用 %clear 指令。例如:

// example.c

void add(double a, double b, double *OUTPUT) {
    *OUTPUT = a + b;
}

void sub(double a, double b, double *OUTPUT) {
    *OUTPUT = a - b;
}
/* example.i */
%module example

%include "typemaps.i"

%{
extern void add(double a, double b, double *OUTPUT);
extern void sub(double a, double b, double *OUTPUT);
%}
extern void add(double a, double b, double *OUTPUT);
%clear double *OUTPUT;      // Remove all typemaps for double *result
extern void sub(double a, double b, double *OUTPUT);

 

 

 这样第三个参数就变成了正常的需要传递的参数了,不是返回的结果了。

2.1.2 输入参数

以下类型映射告诉 SWIG,指针实际上仅保存一个输入值:

int *INPUT
short *INPUT
long *INPUT
unsigned int *INPUT
unsigned short *INPUT
unsigned long *INPUT
double *INPUT
float *INPUT

使用时,它允许传递值而不是指针。例如,考虑以下函数:

// example.c

double add(double *a, double *b) {
    return *a + *b;
}
/* example.i */
%module example

%include "typemaps.i"

%{
extern double add(double *, double *);     // 参数可以只声明类型,不带参数
%}
extern double add(double *INPUT, double *INPUT);  
# script.py

import example
print(example.add(3, 4))  # 7.0

2.1.3 输出参数

以下类型映射规则告诉 SWIG,指针是函数的输出值。使用时,在调用函数时不需要提供参数。而是返回一个或多个输出值。

int *OUTPUT
short *OUTPUT
long *OUTPUT
unsigned int *OUTPUT
unsigned short *OUTPUT
unsigned long *OUTPUT
double *OUTPUT
float *OUTPUT

可以如先前示例中所示使用这些方法。例如,如果你具有以下 C 函数:

// example.c

void add(double a, double b, double *c) {
    *c = a + b;
}
/* example.i */
%module example

%include "typemaps.i"

%{
extern void add(double, double, double *);
%}
extern void add(double a, double b, double *OUTPUT);
# script.py

import example
print(example.add(3, 4))  # 7.0

如果函数还返回值,则将其与参数一起返回。例如,如果你有:

// example.c

double add(double a, double b, double *c) {
    *c = a + b;
    return a;
}
/* example.i */
%module example

%include "typemaps.i"

%{
extern double add(double, double, double *);
%}
extern double add(double a, double b, double *OUTPUT);
# script.py

import example

func, intP = example.add(3, 4)
print(func, intP)  # 3.0  7.0  第一个返回值是函数的返回值,第二个返回值是 OUTPUT 的值

2.1.4 输入 / 输出参数

当指针既用作输入值又用作输出值时,可以使用以下类型映射:

int *INOUT
short *INOUT
long *INOUT
unsigned int *INOUT
unsigned short *INOUT
unsigned long *INOUT
double *INOUT
float *INOUT

有这样一个 C 函数:

// example.c

void negate(double *x) {
    *x = -(*x);
}

要使 x 既作为函数的输入值又作为输出值,请在接口文件中声明如下函数:

/* example.i */
%module example

%include "typemaps.i"

%{
extern void negate(double *);
%}
extern void negate(double *INOUT);
# script.py

import example
print(example.negate(3))   # -3.0

INOUT 规则的一个细微之处是,许多脚本语言对原始对象实施了可变性约束(这意味着简单的对象,如整数和字符串,不应更改)。因此,不能像在此示例中基础 C 函数那样就地修改对象的值。因此,INOUT 规则将修改后的值作为新对象返回,而不是直接覆盖原始输入对象的值。

2.1.5 使用不同的名称

// example.c

void negate(double *x) {
    *x = -(*x);
}

void add(double a, double b, double *OUTPUT) {
    *OUTPUT = a + b;
}

double sub(double *a, double *b) {
    return *a - *b;
}
/* example.i */
%module example

%include "typemaps.i"

%{
extern void negate(double *);
extern void add(double, double, double *);
extern double sub(double *, double *);
%}
extern void negate(double *INOUT);
extern void add(double a, double b, double *OUTPUT);
extern double sub(double *INPUT, double *INPUT);

// 可以上面这样使用,也可以使用 %apply
# script.py

import example

print(example.negate(3))  # -3.0
print(example.add(3, 4))  # 7.0
print(example.sub(3, 4))  # -1.0

2.2 对输入值施加约束

    除了更改对各种输入值的处理之外,还可以使用类型映射来应用约束。例如,也许你想确保一个值是正数,或者一个指针是非 NULL 的。这可以通过包含 constraints.i 库文件来完成。个人认为,几乎用不到这个功能

2.2.1 constraints.i

以下接口文件是对 constraints.i 库最好的说明, 该文件的行为与期望的完全一样。如果任何参数违反约束条件,将引发脚本语言异常。最终,可能会捕获错误的值,防止神秘的程序崩溃,等等。

%include "constraints.i"
    
double exp(double x);
double log(double POSITIVE);         // Allow only positive values
double sqrt(double NONNEGATIVE);     // Non-negative values only
double inv(double NONZERO);          // Non-zero values
void   free(void *NONNULL);          // Non-NULL pointers only
POSITIVE                     Any number > 0 (not zero)
NEGATIVE                     Any number < 0 (not zero)
NONNEGATIVE                  Any number >= 0
NONPOSITIVE                  Any number <= 0
NONZERO                      Nonzero number
NONNULL                      Non-NULL pointer (pointers only).

2.2.2 对新的数据类型应用约束

约束库仅支持原始 C 数据类型,但使用 %apply 可以很容易地将其应用于新的数据类型。例如 :

// Apply a constraint to a Real variable
%apply Number POSITIVE { Real in };
 
// Apply a constraint to a pointer type
%apply Pointer NONNULL { Vector * };

特殊类型的 Number 和 Pointer 可以分别应用于任何数字和指针变量类型。为了以后删除约束,可以使用 %clear 指令:

%clear Real in;
%clear Vector *;

 

posted on 2022-11-23 16:02  软饭攻城狮  阅读(258)  评论(0编辑  收藏  举报

导航