11.3 模式匹配规则

本节描述C/C++类型关联typemap的模式匹配规则。实践中使用调试选项监测模式匹配规则也将会讲述。

11.3.1 基础匹配规则

Typemap同时使用类型和名称(参数名)进行匹配。对于给定TYPE NAME对,使用如下规则查找匹配。第一个找到的typemap的先被使用。

  • 精确匹配 TYPENAME的typemap
  • 仅仅精确匹配TYPEE*的typemap
  • 如果TYPE是C++模板类型T < TPARMS >,TPARMS 为模板参数,类型从模板参数中剔出来,使用如下的规则:
    • 精确匹配 TYPENAME的typemap
    • 仅仅精确匹配TYPEE*的typemap

如果TYPE包含修饰符(const、volatile等),一次去掉(strip)一个修饰符形成新的类型,使用上面的规则进行匹配。左边的修饰符最先被去掉,最右边的最后被去掉。例如int const*const第一次被去除修饰符后变成int *const,接下来变成int *

如果TYPE是数组,使用如下的转换:

  • 将所有的维度都替换为[ANY],得到通用的数组typemap

为说明问题,假设有下面的代码:

  1. int foo(const char *s);

为了给const char *s找到合适的typemap,SWIG将搜索如下的typemap:

  1. const char *s // Exact type and name match
  2. const char * // Exact type match
  3. char *s // Type and name match (qualifier stripped)
  4. char * // Type match (qualifier stripped)

当找到多于一个的typemap时,只使用第一个匹配。下面这个例子展示了一些应用基础匹配规则的例子:

  1. %typemap(in) int *x {
  2. ... typemap 1
  3. }
  4. %typemap(in) int * {
  5. ... typemap 2
  6. }
  7. %typemap(in) const int *z {
  8. ... typemap 3
  9. }
  10. %typemap(in) int [4] {
  11. ... typemap 4
  12. }
  13. %typemap(in) int [ANY] {
  14. ... typemap 5
  15. }
  16. void A(int *x); // int *x rule (typemap 1)
  17. void B(int *y); // int * rule (typemap 2)
  18. void C(const int *x); // int *x rule (typemap 1)
  19. void D(const int *z); // const int *z rule (typemap 3)
  20. void E(int x[4]); // int [4] rule (typemap 4)
  21. void F(int x[1000]); // int [ANY] rule (typemap 5)

兼容性注释:SWIG-2.0.0引入一次剔除一个修饰符的规则。先前的版本一次将所有的修饰符都剔除了。

11.3.2 Typedef匹配规约(reduction)

如果使用前面的规则没有找到任何匹配,SWIG应用typedef匹配规约,然后在规约后的类型上继续使用一样的规则重复查找。为演示,假设有如下代码:

  1. %typemap(in) int {
  2. ... typemap 1
  3. }
  4. typedef int Integer;
  5. void blah(Integer x);

为找到Integer x的typemap,SWIG首先查找如下typemap:

  1. Integer x
  2. Integer

没找到的话,使用Integer -> int规约,然后重复匹配:

  1. int x
  2. int --> match: typemap 1

即使通过typedef,两个类型是一样的,SWIG还是允许为它们分别定义不同的typemap。这个特性允许你对自己感兴趣的类型自定义单独的typemap。例如你写了如下代码:

  1. typedef double pdouble; // Positive double
  2. // typemap 1
  3. %typemap(in) double {
  4. ... get a double ...
  5. }
  6. // typemap 2
  7. %typemap(in) pdouble {
  8. ... get a positive double ...
  9. }
  10. double sin(double x); // typemap 1
  11. pdouble sqrt(pdouble x); // typemap 2

当规约类型时,一次应用一次typedef规约。匹配过程会一直进行下去,除非找到活没有更多的规约可用。

对于复杂类型,规约过程可能会生成一长串模式。考虑如下:

  1. typedef int Integer;
  2. typedef Integer Row4[4];
  3. void foo(Row4 rows[10]);

为匹配Row4 rows[10]参数,SWIG可能检查如下模式,直到它找到合适的匹配:

  1. Row4 rows[10]
  2. Row4 [10]
  3. Row4 rows[ANY]
  4. Row4 [ANY]
  5. # Reduce Row4 --> Integer[4]
  6. Integer rows[10][4]
  7. Integer [10][4]
  8. Integer rows[ANY][ANY]
  9. Integer [ANY][ANY]
  10. # Reduce Integer --> int
  11. int rows[10][4]
  12. int [10][4]
  13. int rows[ANY][ANY]
  14. int [ANY][ANY]

对于像模板这样的参数化类型,情况更复杂。假设有如下的声明:

  1. typedef int Integer;
  2. typedef foo<Integer,Integer> fooii;
  3. void blah(fooii *x);

如下的typemap模式将会被搜索,用于匹配参数fooii *x

  1. fooii *x
  2. fooii *
  3. # Reduce fooii --> foo<Integer,Integer>
  4. foo<Integer,Integer> *x
  5. foo<Integer,Integer> *
  6. # Reduce Integer -> int
  7. foo<int, Integer> *x
  8. foo<int, Integer> *
  9. # Reduce Integer -> int
  10. foo<int, int> *x
  11. foo<int, int> *

Typemap规约一般总是应用于最左边的类型。只有最左边的的不能匹配了才会向右规约。这种行为意味着你可以为foo<int,Integer>定义一个typemap,这样的话foo<Integer,int>typemap将永远不会匹配。这个技巧很少有人了解,实践中也很少有人这么干。当然,你可以使用这个技巧迷惑你的同事。

作为澄清,值得强调的是typedef匹配仅仅是typedef规约的过程,SWIG并不会搜索每一个可能的typedef。==假设声明了一个类型,它只会规约类型,不会在查找它的typedef定义==^luoxiangyong-note-type-reduction。例如,对于类型Struct,下面的typemap不会用于aStruct参数,因为Struct已经全部规约了。

  1. struct Struct {...};
  2. typedef Struct StructTypedef;
  3. %typemap(in) StructTypedef {
  4. ...
  5. }
  6. void go(Struct aStruct);

11.3.3 默认的typemap匹配规则

如果即使使用typedef规约,基础匹配规则也没有找到合适的匹配,SWIG就会使用默认的匹配规则查找合适的typemap。这些通用typemap基于SWIGTYPE基础类型。例如,指针使用SWIGTYPE *,参考使用SWIGTYPE *。更确切地说应该是,这些规则基于C++模板偏特化(template partial specialization)匹配规则,C++编译器使用这种规则查找合适的偏特化模板。这意味着匹配从一般typemap中选择最特化的版本使用。例如,当查找int const *时,根据规则,会在匹配SWIGTYPE *之前先匹配SWIGTYPE const *,会在匹配SWIGTYP之前先匹配SWIGTYPE *

大多数的SWIG语言模块针对C语言的原始类型(primitive types)都定义了默认的typemap。这些定义全部都比较直接。例如,针对原始类型的值或const引用的列集可能如下编写:

  1. %typemap(in) int "... convert to int ...";
  2. %typemap(in) short "... convert to short ...";
  3. %typemap(in) float "... convert to float ...";
  4. ...
  5. %typemap(in) const int & "... convert ...";
  6. %typemap(in) const short & "... convert ...";
  7. %typemap(in) const float & "... convert ...";
  8. ...

因为typemap匹配所有的typedef声明,通过值或const引用定义的任何原始类型的typedef都可以使用这些定义。绝大部分的目标语言模块同时还为char指针和char数组定义了typemap用以处理字符串,所以这样的非默认的类型也同样可以像原始类型一样使用基础的typemap,它们提供了比默认typemap更好的匹配规则。

下面是一组语言模块提供典型的默认类型,%typemap(“in”)的定义:

  1. %typemap(in) SWIGTYPE & { ... default reference handling ... };
  2. %typemap(in) SWIGTYPE * { ... default pointer handling ... };
  3. %typemap(in) SWIGTYPE *const { ... default pointer const handling ... };
  4. %typemap(in) SWIGTYPE *const& { ... default pointer const reference handling ... };
  5. %typemap(in) SWIGTYPE[ANY] { ... 1D fixed size arrays handlling ... };
  6. %typemap(in) SWIGTYPE [] { ... unknown sized array handling ... };
  7. %typemap(in) enum SWIGTYPE { ... default handling for enum values ... };
  8. %typemap(in) const enum SWIGTYPE & { ... default handling for const enum reference values ... };
  9. %typemap(in) SWIGTYPE (CLASS::*) { ... default pointer member handling ... };
  10. %typemap(in) SWIGTYPE { ... simple default handling ... };

如果你想更改SWIG对简单指针的处理方式,你可以重新定义SWIGTYPE *。需要注意的是,简单默认的typemap规则用于匹配简单类型,不用用于匹配其他规则:

  1. %typemap(in) SWIGTYPE { ... simple default handling ... }

这个typemap非常重要,因为当调用或放回值类型时就会触发它。例如,如果你有如下声明:

  1. double dot_product(Vector a, Vector b);

Vector类型就会匹配SWIGTYPESWIGTYPE的默认实现就是讲值类型转换为指针类型(前面的章节讲过)。

通过重新定义SWIGTYPE类型,可以实现其他的行为。例如,如果你清除了所有针对SWIGTYPE的typemap,SWIG将不能包装未知的数据类型(这些类型可能对调试来说比较重要)了。然而,你可以修改SWIGTYPE,将对象列集为对象而不是转换为指针。

考虑如下的typemap定义,SWIG会为enum查找最佳的匹配,代码如下:

  1. %typemap(in) const Hello & { ... }
  2. %typemap(in) const enum SWIGTYPE & { ... }
  3. %typemap(in) enum SWIGTYPE & { ... }
  4. %typemap(in) SWIGTYPE & { ... }
  5. %typemap(in) SWIGTYPE { ... }
  6. enum Hello {};
  7. const Hello &hi;

那么最上面的typemap将会被选择,不因为它最先被定义,而是因为它是被包装类型的最贴切的匹配。如果上面的类型没有定义,就会选择使用接下来的类型。

探究默认typemap的最佳方式就是查看相关目标语言的模块定义。这些typemap的定义一般放在SWIG库路径下的java.swg,csharp.swg等文件中。但是,对许多目标语言来说,这些typemap定义都隐藏在复杂的宏定义中,因此,查看这些默认typemap的比较好地方式是查看预处理器的输出,可以通过在接口文件上运行swig -E命令达此目的。实践中,最好的方式是通过调试typemap的模式匹配选项,后面会讲到。

兼容性注释:默认的typemap匹配规则在SWIG-2.0.0版本做了调整,将简单的匹配方案调整为当前的使用C++的类模板偏特化匹配规则。

11.3.4 多个参数的typemap

当指定多个参数的typemap时,它的优先级要高于但给参数的typemap。例如:

  1. %typemap(in) (char *buffer, int len) {
  2. // typemap 1
  3. }
  4. %typemap(in) char *buffer {
  5. // typemap 2
  6. }
  7. void foo(char *buffer, int len, int count); // (char *buffer, int len)
  8. void bar(char *buffer, int blah); // char *buffer

多个参数的typemap写匹配限制更多。所有的类型和参数都必须匹配。

11.3.5 同C++模板的匹配方式的对比

11.3.6 调试typemap的模式匹配

有两个用于调试的命令行参数可用于调试typemap,-debug-tmsearch-debug-tmused