前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SWIG 官方文档第二部分 - 机翻中文人肉修正

SWIG 官方文档第二部分 - 机翻中文人肉修正

作者头像
韩伟
发布2021-09-03 15:46:17
2.1K0
发布2021-09-03 15:46:17
举报
文章被收录于专栏:韩伟的专栏韩伟的专栏

7 SWIG 和 C++11

7.1 简介

本章简要概述了 C++11 标准的 SWIG 实现。SWIG 的这一部分仍在进行中。

SWIG 支持新的 C++ 语法更改,但在某些领域(例如 decltype 表达式和可变参数模板)有一些小限制。新 STL 类型(unordered_container、result_of、tuples)的包装器不完整。新容器的包装器的工作方式与 C++03 容器非常相似,欢迎用户通过调整现有容器接口文件并将它们作为补丁提交以包含在 SWIG 的未来版本中来提供帮助。

7.2 核心语言变化

7.2.1 右值引用和移动语义

SWIG 正确解析了右值引用语法“&&”,例如它在下面的移动构造函数和移动赋值运算符中的典型用法:

C++class MyClass { ... std::vector<int> numbers; public: MyClass(MyClass &&other) : numbers(std::move(other.numbers)) {} MyClass & operator=(MyClass &&other) { numbers = std::move(other.numbers); return *this; } };

右值引用是为 C++ 临时对象设计的,因此在非 C++ 目标语言中使用时不是很有用。通常,您会在解析类之前通过 %ignore 忽略它们。例如,忽略移动构造函数:

C++%ignore MyClass::MyClass(MyClass &&);

计划是在 SWIG 的未来版本中默认忽略移动构造函数。请注意,在大多数目标语言中,默认情况下会忽略普通赋值运算符和移动赋值运算符,并显示以下警告:

example.i:18:警告 503:除非重命名为有效标识符,否则无法包装“operator =”。

7.2.2 广义常量表达式

SWIG 解析并识别关键字constexpr,但无法充分利用它。这些 C++ 编译时常量可用作目标语言的运行时常量。下面显示了从编译时常量函数分配 C++ 编译时常量的示例用法:

C++constexpr int XXX() { return 10; } constexpr int YYY = XXX() + 100;

当从目标语言使用其中任何一个时,将进行运行时调用以获取基础常量。

7.2.3 外部模板

SWIG 能正确的解析关键字 extern template。然而,这个模板实例化阻碍了没有相关 C++ 编译器以外的翻译单元,并且因此不会被 SWIG 使用。SWIG 只使用 %template 来实例化和包装模板。

C++tempate class std::vector<int>; // C++ 03 显式实例化在 C++ extern template class std::vector<int>; // C++11 显式实例化抑制在 C++ %template(VectorInt) std::vector<int>; // SWIG 实例化

7.2.4 初始化列表

初始化器列表在很大程度上是一种 C++ 编译器构造,不能从包装器中轻松访问,因为它们旨在用于使用特殊std::initializer_list 类型的类的编译时初始化。SWIG 检测初始化列表的使用,并在每次使用时发出一个特殊的信息警告:

Plain Textexample.i:33: Warning 476: Initialization using std::initializer_list.

初始化列表通常出现在构造函数中,但可以出现在任何函数或方法中。它们经常出现在构造函数中,这些构造函数重载了用于初始化类的替代方法,例如用于向容器添加元素的 std 容器的 push_back 方法。推荐的方法是简单地忽略初始化列表构造函数,例如:

C++%ignore Container::Container(std::initializer_list<int>);class Container {public: Container(std::initializer_list<int>); // initializer-list constructor Container(); void push_back(const int &); ...};

或者,您可以修改该类并通过其他方式添加另一个用于初始化的构造函数,例如通过 std::vector:

C++%include <std_vector.i>class Container {public: Container(const std::vector<int> &); Container(std::initializer_list<int>); // initializer-list constructor Container(); void push_back(const int &); ...};

然后从您的目标语言调用此构造函数,例如,在 Python 中,以下将调用带有 std::vector的构造函数:

Python>>> c = Container( [1, 2, 3, 4] )

如果您无法修改被包装的类,请考虑忽略初始化列表构造函数并使用 %extend 添加替代构造函数:

C++%include <std_vector.i> %extend Container { Container(const std::vector<int> &elements) { Container *c = new Container(); for (int element : elements) c->push_back(element); return c; } } %ignore Container::Container(std::initializer_list<int>); class Container {public: Container(std::initializer_list<int>);// 初始化列表构造函数 Container(); void push_back(const int &); ... };

上面使包装器看起来好像类已经声明如下:

C++%include <std_vector.i>class Container {public: Container(const std::vector<int> &);// Container(std::initializer_list<int>); // initializer-list constructor (ignored) Container(); void push_back(const int &); ...};;

std::initializer_list只是一个只能在编译时初始化的容器。由于它只是一个 C++ 类型,因此可以为目标语言容器编写类型映射以映射到 std::initializer_list。但是,这只能对固定数量的元素执行,因为初始化列表不是设计为在运行时使用可变数量的参数构造的。下面的示例是一种非常简单的方法,它忽略传入的任何参数,仅使用在编译时选择的固定整数值的固定列表进行初始化:

C++%typemap(in) std::initializer_list<int> { $1 = {10, 20, 30, 40, 50}; } class Container { public: Container(std::initializer_list<int>); // 初始化列表构造函数 Container(); void push_back(const int &); ... };

任何从目标语言传入值的尝试都将被忽略并替换为{10, 20, 30, 40, 50}. 不用说,这个方法非常有限,但是可以改善,但是仅仅是一点。一个 typemap 可以用来映射一个固定数量元素到 std::initializer_list 上,而在运行时进行赋值。此类型映射会由目标语言指定。

请注意,std::initializer_list的默认类型映射只会发出警告,因此任何用户提供的类型映射都将覆盖它并取消警告。

7.2.5 统一初始化

SWIG 完全支持用于成员初始化的大括号 {}:

C++struct BasicStruct { int x; double y; }; struct AltStruct { AltStruct(int x, double y) : x_{x}, y_{y} {} int x_; double y_;}; BasicStruct var1{5, 3.2}; // 只填充结构体组件AltStruct var2{2, 4.3}; // 调用构造函数

统一初始化不会影响目标语言的使用,例如在 Python 中:

Python>>>一个= AltStruct(10,142.15)>>> a.x_ 10 >>> a.y_ 142.15

7.2.6 类型推断

SWIG 支持decltype()有一些限制。允许使用单个变量,但尚不支持表达式。例如,以下代码将起作用:

C++int i;decltype(i) j;

但是,在 decltype 中使用表达式会导致语法错误:

C++int i; int j;decltype(i+j) k; // syntax error

7.2.7 基于范围的 for 循环

此功能仅是实现块的一部分。SWIG 忽略了它。

7.2.8 Lambda 函数和表达式

SWIG 正确解析大部分 Lambda 函数语法。例如:

C++auto val = [] { return something;}; auto sum = [](int x, int y) { return x+y; }; auto sum = [](int x, int y) -> int { return x+y; };

由于目标语言中缺少对闭包(lambda 函数的范围)的支持,因此暂时从包装器中删除了 lambda 函数。

用于创建变量的 Lambda 函数也可以被解析,但由于从表达式推导出类型时对auto 的支持有限,变量被简单地忽略。

C++auto six = [](int x, int y) { return x+y; }(4, 2);

在以后的版本中应该会提供更好的支持。

7.2.9 替代函数语法

SWIG 完全支持函数的新定义。例如:

C++struct SomeStruct { int FuncName(int x, int y); };

现在可以像在 C++11 中那样编写:

C++struct SomeStruct { auto FuncName(int x, int y) -> int; }; auto SomeStruct::FuncName(int x, int y) -> int { return x + y; }

目标语言中的用法保持不变,例如在 Python 中:

Python>>> a = SomeStruct() >>> a.FuncName(10, 5) 15

根据前面描述的限制,SWIG 还将处理返回类型的类型推断。例如:

C++auto square(float a, float b) -> decltype(a);

7.2.10 对象构建改进

对象构造改进分为三个部分。第一个改进是构造函数委托,如下所示:

C++class A {public: int a; int b; int c; A() : A(10) {} A(int aa) : A(aa, 20) {} A(int aa, int bb) : A(aa, bb, 30) {} A(int aa, int bb, int cc) { a=aa; b=bb; c=cc; }};

可以调用对等构造函数的地方。SWIG 可以毫无问题地处理这个问题。

第二个改进是通过using 声明继承构造函数。这被正确解析,但额外的构造函数当前未添加到目标语言中的派生代理类。一个例子如下所示:

C++class BaseClass { public: BaseClass(int iValue); }; class DerivedClass: public BaseClass { public: using BaseClass::BaseClass; // 添加 DerivedClass(int) 构造函数};

最后一部分是声明站点的成员初始化。这种初始化由 SWIG 处理。

C++class SomeClass { public: SomeClass() {} explicit SomeClass(int new_value) : value(new_value) {} int value = 5; };

7.2.11 显式覆盖和最终

特殊标识符final和override可用于方法和析构函数,例如在以下示例中:

C++struct BaseStruct { virtual void ab() const = 0; virtual void cd(); virtual void ef(); virtual ~BaseStruct();};struct DerivedStruct : BaseStruct { virtual void ab() const override; virtual void cd() final; virtual void ef() final override; virtual ~DerivedStruct() override;};

7.2.12 空指针常量

该 nullptr 常数是在包装大多是不重要的。在它有影响的少数地方,它被视为 NULL。

7.2.13 强类型枚举

SWIG 支持强类型枚举并解析新的枚举类语法和枚举的前向声明符,例如:

C++enum class MyEnum : unsigned int;

强类型枚举通常用于避免名称冲突,例如:

C++struct Color { enum class RainbowColors : unsigned int { Red, Orange, Yellow, Green, Blue, Indigo, Violet }; enum class WarmColors { Yellow, Orange, Red }; // Note normal enum enum PrimeColors { Red=100, Green, Blue };};

目标语言有多种处理枚举的方式,因此在本节中无法准确说明它们是如何处理的。然而,一般来说,大多数脚本语言会修改强类型枚举的类名,但不会对普通枚举使用任何额外的修改。例如,在 Python 中,以下代码

Pythonprint Color.RainbowColors_Red、Color.WarmColors_Red、Color.Red

结果是

0 2 100

强类型语言通常将普通枚举包装到一个枚举类中,因此对待普通枚举和强类型枚举是一样的。Java 中的等价物是:

JavaSystem.out.println(Color.RainbowColors.Red.swigValue() + " " + Color.WarmColors.Red.swigValue() + " " + Color.PrimeColors.Red.swigValue());

7.2.14 双尖括号

SWIG 正确解析符号 >> 作为关闭模板块,如果在顶层找到它,或者作为右移运算符 >> 。

C++std::vector<std::vector<int>> myIntTable;

7.2.15 显式转换运算符

除了构造函数之外,SWIG 现在可以正确解析运算符的显式关键字。例如:

C++class U {public: int u;}; class V {public: int v;}; class TestClass {public: //implicit converting constructor TestClass(U const &val) { t=val.u; } // explicit constructor explicit TestClass(V const &val) { t=val.v; } int t;}; struct Testable { // explicit conversion operator explicit operator bool() const { return false; }};

显式构造函数和运算符的效果与代理类几乎没有关系,因为目标语言没有与 C++ 相同的隐式转换概念。转换运算符无论是否显式都需要重命名为有效的标识符名称,以使它们可用作普通代理方法。

7.2.16 类型别名和别名模板

类型别名是以下形式的语句:

C++using PFD = void (*)(double); // 新引入的语法

这相当于旧样式的 typedef:

C++typedef void (*PFD)(double); // 旧样式

以下是别名模板的示例:

C++template< typename T1, typename T2, int N > class SomeType { public: T1 a; T2 b; }; template< typename T2 > using TypedefName = SomeType<char*, T2, 5>;

SWIG 支持类型别名和别名模板。但是,为了使用别名模板,必须使用两个%template指令:

C++%template(SomeTypeBool) SomeType<char*, bool, 5>; %template() TypedefName<bool>;

首先,根据被包装的任何模板,使用目标语言使用的名称实例化实际模板。其次,别名模板需要空模板实例化%template()。第二个要求是将适当的实例化模板类型添加到类型系统中的必要条件,因为 SWIG 不会自动实例化模板。有关包装模板的更多一般信息,请参阅模板部分。

7.2.17 无限制联合

SWIG 完全支持联合内的任何类型,即使它没有定义简单的构造函数。例如,以下代码的包装器正确地提供了对联合中所有成员的访问:

C++struct point { point() {} point(int x, int y) : x_(x), y_(y) {} int x_, y_;}; #include <new> // For placement 'new' in the constructor belowunion P { int z; double w; point p; // Illegal in C++03; legal in C++11. // Due to the point member, a constructor definition is required. P() { new(&p) point(); }} p1;

7.2.18 可变模板

SWIG 支持可变参数模板语法(在 <> 块内部、可变参数类继承和可变参数构造函数和初始化器),但有一些限制。以下代码被正确解析:

C++template <typename... BaseClasses> class ClassName : public BaseClasses... { public: ClassName (BaseClasses &&... baseClasses) : BaseClasses(baseClasses)... {} }

然而,现在%template指令只接受一个参数替换变量模板参数。

C++%template(MyVariant1) ClassName<> // 尚不支持零参数%template(MyVariant2) ClassName<int> // ok %template(MyVariant3) ClassName<int, int> // 尚不支持太多参数

对可变参数sizeof()函数的支持被正确解析:

C++const int SIZE = sizeof...(ClassName<int, int>);

在上面的例子中SIZE当然被包装成一个常量。

7.2.19 新的字符串文字

SWIG 支持宽字符串和 Unicode 字符串常量以及原始字符串文字。

C++// 新的字符串文字wstring aa = L"Wide string"; const char *bb = u8"UTF-8 字符串"; const char16_t *cc = u"UTF-16 字符串"; const char32_t *dd = U"UTF-32 字符串"; // 原始字符串字面量const char *xx = ")I'm an \"ascii\" \\ string."; const char *ee = R"XXX()I'm an "ascii" \ string.)XXX"; // 与 xx 相同wstring ff = LR"XXX(I'm a "raw wide" \ string.)XXX";const char *gg = u8R"XXX(I'm a "raw UTF-8" \ string.)XXX";const char16_t *hh = uR"XXX(I'm a "raw UTF-16" \ string.)XXX";const char32_t *ii = UR"XXX(I'm a "raw UTF-32" \ string.)XXX";

但是,非 ASCII 字符串支持在各种目标语言之间差异很大。

注意:当前存在一个错误,即 SWIG 的预处理器错误地解析原始字符串文字中奇数个双引号。

7.2.20 用户定义的文字

SWIG 解析用户定义文字的声明,即操作符 "" _mysuffix()函数语法。

一些例子是原始文字:

C++OutputType operator "" _myRawLiteral(const char * value);

数字化文字:

C++OutputType operator "" _mySuffixIntegral(unsigned long long);OutputType operator "" _mySuffixFloat(long double);

和字符串化:

C++OutputType operator "" _mySuffix(const char * string_values, size_t num_chars);OutputType operator "" _mySuffix(const wchar_t * string_values, size_t num_chars);OutputType operator "" _mySuffix(const char16_t * string_values, size_t num_chars);OutputType operator "" _mySuffix(const char32_t * string_values, size_t num_chars);

与 SWIG 解析的其他运算符一样,会发出有关重命名运算符以便对其进行包装的警告:

Plain Textexample.i:27: Warning 503: Can't wrap 'operator "" _myRawLiteral' unless renamed to a valid identifier.

如果使用 %rename,则可以像任何其他包装方法一样调用它。当前,您需要指定完整的声明,包括 %rename 的参数:

C++%rename(MyRawLiteral) operator"" _myRawLiteral(const char * value);

或者,如果您只想完全忽略它:

C++%ignore operator "" _myRawLiteral(const char * value);

请注意,使用如下用户定义的文字仍然会出现语法错误:

C++OutputType var1 = "1234"_suffix;OutputType var2 = 1234_suffix;OutputType var3 = 3.1416_suffix;

7.2.21 Thread-local 存储

SWIG 正确解析 thread_local 关键字。例如,当前线程可访问的变量可以定义为:

C++struct A { static thread_local int val; }; thread_local int global_val;

使用thread_local存储说明符不影响包装过程;与未指定时相比,它不会修改包装器代码。如果从目标语言的不同线程访问变量,则变量将是线程本地的,就像从 C++ 代码访问时,变量将是线程本地的一样。

7.2.22 显式默认函数和删除函数

SWIG 处理显式默认的函数,即将= default 添加到函数声明中。删除的定义,也称为删除的函数,在函数声明中添加了= delete。例如:

C++struct NonCopyable { NonCopyable & operator=(const NonCopyable &) = delete; /* 删除 operator= */ NonCopyable(const NonCopyable &) = delete; /* 删除复制构造函数 */ NonCopyable() = default; /* 明确允许空构造函数 */ };

已删除函数的包装器在目标语言中不可用。默认函数的包装器当然可以在目标语言中使用。显式默认函数对 SWIG 包装没有直接影响,因为声明的处理方式与 SWIG 解析的任何其他方法声明非常相似。

删除的函数还旨在防止调用函数时进行隐式转换。例如,C++ 编译器不会编译任何尝试使用 int 作为传递给f的参数类型的代码:

C++struct NoInt { void f(double i); void f(int) = delete;};

这是 C++ 编译时检查,SWIG 不会尝试检测目标语言是否使用 int 而不是 double,因此在这种情况下,完全有可能将 int 而不是 double 传递给 Java、Python 等中的f。

7.2.23 类型 long long int

SWIG 正确解析并使用了不久前在 C99 中引入的新long long类型。

7.2.24 静态断言

SWIG 正确解析新的static_assert声明。这是一个 C++ 编译时指令,因此 SWIG 对它没有任何用处。

C++tempalte<typename T> struct Check { static_assert(sizeof(int) <= sizeof(T),“not engoth”);};

7.2.25 允许 sizeof 在没有显式对象的情况下处理类的成员

SWIG 可以在类型和对象上解析新的 sizeof()。例如:

C++struct A { int member;}; const int SIZE = sizeof(A::member); // 不适用于 C++03。用 C++11 没问题

在 Python 中:

Python>>> SIZE8

7.2.26 异常规范和 noexcept

C++11 在 noexcept 规范中添加到异常规范,以指示函数可能会或可能不会抛出异常,而无需实际命名任何异常。SWIG 理解这些,尽管没有任何有用的方式可以让目标语言利用这些信息,因此在包装过程中忽略它一样好。以下是函数声明中 noexcept 的一些示例:

C++static void noex1() noexcept;int noex2(int) noexcept(true);int noex3(int, bool) noexcept(false);

7.2.27 控制和查询对象对齐方式

一个alignof操作者主要用于内C ++中的字节数返回对准,但是也可以使用初始化变量如下所示。目标语言可以像任何其他变量的编译时初始化值一样访问该变量的值。

C++const int align1 = alignof(A::member);

尚不支持用于变量对齐的alignas说明符。用法示例:

C++struct alignas(16) S { int num; }; alignas(double) unsigned char c[sizeof(double)];

现在使用预处理器来解决这个问题:

C++#define alignas(T)

7.2.28 属性

如下所示的属性尚不受支持,并且会出现语法错误。

C++int [[attr1]] i [[attr2, attr3]]; [[noreturn, nothrow]] void f [[noreturn]]();

7.3 标准库变化

7.3.1 线程工具

SWIG 目前不包装或使用任何引入的新线程类(线程、互斥锁、锁、条件变量、任务)。主要原因是 SWIG 目标语言提供了自己的线程工具,因此它们的用途有限。

7.3.2 元组类型

SWIG 尚未为新元组类型提供库文件。可变模板支持需要进一步的工作来提供大量的元组包装器。

7.3.3 哈希表

STL 中的新哈希表是 unordered_set、 unordered_multiset、unordered_map、unordered_multimap 。这些在 SWIG 中不可用,但原则上应该通过调整当前的 STL 容器来轻松实现。

7.3.4 Regular expressions

虽然 SWIG 可以为新的 C++11 正则表达式类提供包装器,但几乎没有必要,因为目标语言有自己的正则表达式工具。

7.3.5 通用智能指针

SWIG 以与支持boost::shared_ptr相同的方式为std::shared_ptr提供特殊的智能指针处理。请参阅shared_ptr 智能指针库部分。目前还没有可用于std::weak_ptr和std::unique_ptr 的特殊智能指针处理。

7.3.6 可扩展的随机数工具

此功能仅扩展和标准化标准库,不会影响 C++ 语言或 SWIG。

7.3.7 包装参考

包装器引用类似于普通的 C++ 引用,但它是可复制构造和可复制赋值的。可以想象,它们可以用于公共 API。尽管 SWIG 中没有对 std::reference_wrapper 的特殊支持。如果使用包装器引用,用户将需要编写自己的类型映射,这些类似于普通的 C++ 引用类型映射。

7.3.8 函数对象的多态包装器

SWIG 以一种非常自然的方式支持几种语言的函数类。然而,尚未为新的提供任何内容 std::function 模板. SWIG 会像其他模板解析模板用法

C++%rename(__call__) Test::operator(); // 默认重命名用于 Python struct Test { bool operator()(int x, int y); // 函数对象}; #include <function> std::function<void (int, int)> pF = Test; // 函数模板包装器

Python 中支持的普通函子的使用示例如下所示。它不涉及std::function。

Pythont = Test() b = t(1, 2) # 调用 C++ 函数对象

7.3.9 元编程的类型特征

支持 C++ 元编程的 type_traits 函数在编译时很有用,并且专门针对 C++ 开发:

C++#include <type_traits> // 第一种操作方式。template< bool B > struct algorithm { template< class T1, class T2 > static int do_it(T1 &, T2 &) { /*...*/ return 1; } }; // 第二种操作方式。template<> struct algorithm<true> { template< class T1, class T2 > static int do_it(T1, T2) { /*...*/ return 2; } }; // 实例化 'elaborate' 会根据使用的类型自动实例化正确的操作方式。template< class T1, class T2 > int Explain(T1 A, T2 B) { // 仅当 'T1' 是整数且如果 'T2' 是浮点数时才使用第二种方式, // 否则使用第一种方式。 return algorithm< std::is_integral<T1>::value && std::is_floating_point<T2>::value >::do_it(A, B); }

SWIG 正确解析模板特化、模板类型等。但是,元编程和 type_traits 标头中的额外支持实际上是为了编译时,在运行时对于目标语言并没有多大用处。例如,由于 SWIG 需要通过%template显式实例化模板,因此std::is_integral<int>本身不会提供太多内容。但是,使用这种元编程技术的模板函数可能对包装很有用。例如,可以进行以下实例化:

C++%template(Elaborate) elaborate<int, int>; %template(Elaborate) elaborate<int, double>;

然后,可以从目标语言(例如 Python)为上述%template实例化给出的类型子集调用适当的算法:

Python>>> Elaborate(0, 0) 1 >>> Elaborate(0, 0.0) 2

7.3.10 计算函数对象返回类型的统一方法

<function> 头文件中引入的新std::result_of类提供了一种通过std::result_of::type获取函数类型的返回类型的通用方法。没有任何库接口文件来支持这种类型。通过一些工作,SWIG 将使用下面显示的方法推导出在std::result_of 中使用时函数的返回类型。该技术基本上向前声明了std::result_of模板类,然后部分地将其专门用于感兴趣的函数类型。SWIG 将使用部分特化,因此正确使用部分特化中提供的 std::result_of::type。

C++%inline %{ #include <function> typedef double(*fn_ptr)(double); %} namespace std { // 前向声明 result_of template<typename Func> struct result_of; // 添加 result_of 的部分特化 template<> struct result_of< fn_ptr(double) > { typedef double type; }; } %template() std::result_of< fn_ptr(double) >; %inline %{ double square(double x) { return (x * x); } template<class Fun, class Arg> typename std::result_of<Fun(Arg)>::type test_result_impl(Fun fun, Arg arg) { return fun(arg); } } %} %template(test_result) test_result_impl< fn_ptr, double >; %constant double (*SQUARE)(double) = square;

请注意SWIG 需要实例化模板的第一次使用 %template。空模板实例化就足够了,因为std::result_of<Fun(Arg)>::type 不需要代理类,因为这种类型实际上只是一个 double。第二个 %template 实例化被包装用作回调的模板函数。然后可以将 %constant 用于任何回调函数,如指向函数和回调的指针中所述。

Python 的示例用法应该给出不太令人惊讶的结果:

Python>>> test_result(SQUARE, 5.0) 25.0

呼,要使回调正常工作需要付出很多努力。您可以选择更具吸引力的选项,即在函数声明中使用double作为返回类型而不是result_of !

8 预处理

SWIG 包括其自己的 C 预处理器的增强版本。预处理器支持标准的预处理器指令和宏扩展规则。但是,已经进行了许多修改和增强。本章描述了其中的一些修改。

8.1 文件包含

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

C++%include "pointer.i"

不同于#include ,%include 只包含每个文件一次(并在后续将不重新加载该文件%include 声明)。因此,没有必要在 SWIG 接口中使用包含保护。

默认情况下,#include 将被忽略,除非您使用 -includeall 选项运行 SWIG 。忽略传统包含的原因是您通常不希望 SWIG 尝试包装标准头系统头文件和辅助文件中包含的所有内容。

8.2 文件导入

SWIG 提供了另一个带有 %import 指令的文件包含指令。例如:

C++%import "foo.i"

%import 的目的是从另一个 SWIG 接口文件或头文件中收集某些信息,而无需实际生成任何包装器代码。此类信息通常包括类型声明(例如typedef)以及可能用作接口中类声明的基类的 C++ 类。当 SWIG 用于生成扩展作为相关模块的集合时,%import 的使用也很重要。这是一个高级主题,稍后将在“使用模块”一章中进行介绍。

该 -importall 指令告诉 SWIG 遵循所有的 #include 语句导入。如果您想从系统头文件中提取类型定义而不生成任何包装器,这可能很有用。

8.3 条件编译

SWIG 完全支持使用#if、#ifdef、 #ifndef、#else、#endif来有条件地包含接口的一部分。SWIG 在解析接口时预定义了以下符号:

Plain TextSWIG Always defined when SWIG is processing a fileSWIGIMPORTED Defined when SWIG is importing a file with %importSWIG_VERSION Hexadecimal (binary-coded decimal) number containing SWIG version, such as 0x010311 (corresponding to SWIG-1.3.11). SWIGALLEGROCL Defined when using Allegro CLSWIGCFFI Defined when using CFFISWIGCHICKEN Defined when using CHICKENSWIGCLISP Defined when using CLISPSWIGCSHARP Defined when using C#SWIGGUILE Defined when using GuileSWIGJAVA Defined when using JavaSWIGJAVASCRIPT Defined when using JavascriptSWIG_JAVASCRIPT_JSC Defined when using Javascript for JavascriptCoreSWIG_JAVASCRIPT_V8 Defined when using Javascript for v8 or node.js SWIGLUA Defined when using LuaSWIGMODULA3 Defined when using Modula-3SWIGMZSCHEME Defined when using MzschemeSWIGOCAML Defined when using OcamlSWIGOCTAVE Defined when using OctaveSWIGPERL Defined when using PerlSWIGPHP Defined when using PHP5 or PHP7SWIGPHP5 Defined when using PHP5SWIGPHP7 Defined when using PHP7SWIGPIKE Defined when using PikeSWIGPYTHON Defined when using PythonSWIGR Defined when using RSWIGRUBY Defined when using RubySWIGSCILAB Defined when using ScilabSWIGSEXP Defined when using S-expressionsSWIGTCL Defined when using TclSWIGXML Defined when using XML

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

Plain Text__LINE__ 当前行号__FILE__ 当前文件名__STDC__ 定义为指示 ANSI C __cplusplus 使用 -c++ 选项时定义

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

8.4 宏扩展

传统的预处理器宏可用于 SWIG 接口。请注意,# define 语句也用于尝试和检测常量。因此,如果您的文件中有这样的内容,

C++#ifndef _FOO_H 1 #define _FOO_H 1 ... #endif

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

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

C++#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 扩展。

8.5 SWIG 宏

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

C++%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)

%define 的主要目的是定义大型代码宏。与普通的 C 预处理器宏不同,没有必要用连续字符 (\) 终止每一行——宏定义扩展到 %enddef 的第一次出现。此外,当这些宏被扩展时,它们会通过 C 预处理器重新解析。因此,除嵌套的 %define语句外,SWIG 宏可以包含所有其他预处理器指令。

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

8.6 C99 和 GNU 扩展

SWIG-1.3.12 和更新版本支持可变参数预处理器宏。例如:

C++#define DEBUGF(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)

在使用时,任何额外的参数给...放入特殊变量__VA_ARGS__。这也适用于使用%define 定义的特殊 SWIG 宏。

SWIG 允许可变数量的参数为空。但是,这通常会导致结果扩展中出现额外的逗号 (, ) 和语法错误。例如:

C++DEBUGF("hello"); --> fprintf(stderr, "hello", );

要去掉多余的逗号,请像这样使用##:

C++#define DEBUGF(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)

SWIG 还支持 GNU 风格的可变参数宏。例如:

C++#define DEBUGF(fmt, args...) fprintf(stdout, fmt, args)

评论:不完全清楚可变参数宏如何对接口构建有用。但是,它们在内部用于实现许多 SWIG 指令,并提供它们以使 SWIG 与 C99 代码更兼容。

8.7 预处理和定界符

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

8.7.1 预处理和 %{ ... %} & " ... " 分隔符

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

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

%{ ... %}块的内容不加修改地复制到输出(包括所有预处理器指令)。

8.7.2 预处理和 { ... } 分隔符

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

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

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

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

SWIG 会去掉额外的 % 然后留下预处理指令在代码里。

8.8 预处理器和类型图

类型映射支持一个称为 noblock 的特殊属性,其中可以使用 { ... } 分隔符,但分隔符实际上并未生成到代码中。效果然后类似于使用“”或%{%}定界符但是代码通过预处理器运行。例如:

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

可能会产生

C++ { arg1=(int)jarg1; }

然而

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

可能会产生

C++arg1=(int)jarg1;

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

会产生

C++arg1=SWIG_macro(int);

8.9 查看预处理器输出

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

8.10 #error 和 #warning 指令

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

C++#error "这是一条致命错误消息" #warning "这是一条警告消息"

该 #error 行为可以做出像 #warning 一样的效果,如果使用命令行选项 -cpperraswarn 。或者,#pragma 指令可用于达到相同的效果,例如:

C++ /* 修改行为:#error 不会导致 SWIG 以错误退出 */ #pragma SWIG cpperraswarn=1 /* 正常行为:#error 确实导致 SWIG 以错误退出 */ #pragma SWIG cpperraswarn=0

9 SWIG 库

为了帮助构建扩展模块,SWIG 打包了一个支持文件库,您可以将其包含在自己的接口中。这些文件通常定义新的 SWIG 指令或提供可用于访问部分标准 C 和 C++ 库的实用程序函数。本章提供了对当前支持的库文件集的参考。

兼容性说明:旧版本的 SWIG 包含许多用于操作指针、数组和其他结构的库文件。大多数这些文件现在已被弃用并已从发行版中删除。替代库提供了类似的功能。如果您使用旧库,请仔细阅读本章。

9.1 %include 指令和库搜索路径

使用 %include 指令包含库文件。搜索文件时,按以下顺序搜索目录:

1. 当前目录

2. 使用 - I命令行选项指定的目录

3. . /swig_lib

4. swig -swiglib 报告的 SWIG 库安装位置,例如/usr/local/share/swig/1.3.30

5. 在 Windows 上,还会搜索与 swig.exe位置相关的目录Lib。

在第 3-5 点提到的目录中,SWIG 首先查找与目标语言(例如,python、 tcl等)对应的子目录。如果找到,SWIG 将首先搜索特定于语言的目录。这允许库文件的特定语言实现。

您可以通过设置 SWIG_LIB 环境变量来忽略已安装的 SWIG 库。设置环境变量以保存替代库目录。

使用 -verbose 命令行选项时会显示搜索到的目录。

9.2 C 数组和指针

本节描述用于操作低级 C 数组和指针的库模块。这些模块的主要用途是支持操作诸如 int *、 double *或 void * 之类的裸指针的 C 声明。这些模块可用于分配内存、制造指针、取消引用内存以及将指针包装为类对象。由于这些函数提供对内存的直接访问,因此它们的使用可能不安全,您应该谨慎行事。

9.2.1 cpointer.i

所述 cpointer.i 模块定义宏可用于用来产生围绕简单 C 指针包装。该模块的主要用途是生成指向原始数据类型(如 int 和 double )的指针。

%pointer_functions(type,name)

生成用于操作指针类型 *的四个函数的集合:

type *new_name()

创建一个类型为type的新对象并返回一个指向它的指针。在 C 中,对象是使用calloc()创建的。在 C++ 中,使用 new。

type *copy_name(type value)

创建一个类型为type的新对象并返回一个指向它的指针。通过从value复制它来设置初始值。在 C 中,对象是使用calloc()创建的。在 C++ 中,使用new。

type *delete_name(type *obj)

删除一个对象类型type。

void name_assign(type *obj, type value)

分配 *obj = value。

type name_value(type *obj)

返回*obj的值。

使用此宏时,类型可以是任何类型,名称必须是目标语言中的合法标识符。name不应与接口文件中使用的任何其他名称相对应。

这是使用 %pointer_functions() 的简单示例:

C++%module example%include "cpointer.i" /* Create some functions for working with "int *" */%pointer_functions(int, intp); /* A function that uses an "int *" */void add(int x, int y, int *result);

现在,在 Python 中:

Python>>> import example >>> c = example.new_intp() # 创建一个“int”来存储结果>>> example.add(3, 4, c) # 调用函数>>> example.intp_value(c) #取消引用7 >>> example.delete_intp(c) # 删除

%pointer_class(type,name)

将* 类型的指针包装在基于类的接口中。这个接口如下:

C++struct name{ name();// 创建指针对象 ~name(); // 删除指针对象 void assign(type value); // 赋值 type value(); // 获取值 type *cast(); // 将指针转换为原始类型 static name *frompointer(type *); // 从现有的 // 指针创建类包装器};

使用此宏时,类型被限制为简单的类型名称,如 int、float 或 Foo。不允许使用指针和其他复杂类型。name 必须是尚未使用的有效标识符。当一个指针被包装为一个类时,“类”可以透明地传递给任何需要该指针的函数。

如果目标语言不支持代理类,则使用此宏将生成与 %pointer_functions() 宏相同的示例函数。

应该注意的是,类接口确实引入了一个新对象或将一个指针包裹在一个特殊的结构中。相反,直接使用原始指针。

这是使用类代替的相同示例:

C++%module example %include "cpointer.i" /* 将类接口包裹在 "int *" */ %pointer_class(int, intp); /* 一个使用“int *”的函数 */ void add(int x, int y, int *result);

现在,在 Python 中(使用代理类)

Python>>> import example >>> c = example.intp() # 创建一个“int”来存储结果>>> example.add(3, 4, c) # 调用函数>>> c.value() # 解引用7

在这两个宏中,%pointer_class 在使用简单指针时可能是最方便的。这是因为指针像对象一样访问,并且它们可以很容易地被垃圾收集(指针对象的销毁会破坏底层对象)。

%pointer_cast(type1, type2, name)

创建一个将 type1 转换为 type2 的转换函数。函数的名称是name。例如:

C++%pointer_cast(int *, unsigned int *, int_to_uint);

在此示例中,函数int_to_uint()将用于在目标语言中强制转换类型。

注意:这些宏都不能用于安全地处理字符串(char *或char **)。

注意:当使用简单的指针时,通常可以使用类型映射来提供更无缝的操作。

9.2.2 carrays.i

该模块定义了帮助将普通 C 指针包装为数组的宏。该模块不提供任何安全性或额外的包装层——它仅提供用于创建、销毁和修改原始 C 数组数据内容的功能。

%array_functions(type, name)

创建四个函数。

type *new_name(int nelements)

创建类型为 type 的对象的新数组。在 C 中,数组是使用 calloc() 分配的。在 C++ 中,使用new []。

type *delete_name(type *ary)

删除一个数组。在 C 中,使用free()。在 C++ 中,使用delete []。

type name_getitem(type *ary, int index)

返回 ary[index] 的值

void name_setitem(type *ary, int index, type value)

使 ARY[index] = value

使用此宏时,类型可以是任何类型,名称必须是目标语言中的合法标识符。name不应与接口文件中使用的任何其他名称相对应。

这是 %array_functions() 的示例。假设你有一个这样的函数:

C++void print_array(double x[10]) { int i; for (i = 0; i < 10; i++) { printf("[%d] = %g\n", i, x[i]); } }

要包装它,你可以这样写:

C++%module example %include "carrays.i" %array_functions(double, doubleArray); void print_array(double x[10]);

现在,在脚本语言里,你可以这样写:

Pythona = new_doubleArray(10) # Create an arrayfor i in range(0, 10): doubleArray_setitem(a, i, 2*i) # Set a valueprint_array(a) # Pass to Cdelete_doubleArray(a) # Destroy array

%array_class(type, name)

将* 类型的指针包装在基于类的接口中。这个接口如下:

C++struct name { name(int nelements); // Create an array ~name(); // Delete array type getitem(int index); // Return item void setitem(int index, type value); // Set item type *cast(); // Cast to original type static name *frompointer(type *); // Create class wrapper from // existing pointer};

使用此宏时,类型被限制为简单的类型名称,如 int 或 float。不允许使用指针和其他复杂类型。name 必须是尚未使用的有效标识符。当一个指针被包装为一个类时,它可以透明地传递给任何需要该指针的函数。

当与代理类结合使用时,%array_class() 宏会特别有用。例如:

C++%module example%include "carrays.i" %array_class(double, doubleArray); void print_array(double x[10]);

允许您执行以下操作:

Pythonimport example c = example.doubleArray(10) # Create double[10] for i in range(0, 10): c[i] = 2*i # 赋值example.print_array(c) # 传递给 C

注意:这些宏没有将 C 数组封装在特殊的数据结构或代理中。没有任何类型的边界检查或安全性。如果你想要这个,你应该考虑使用一个特殊的数组对象而不是一个裸指针。

注意:%array_functions() 和 %array_class() 不应与 char 或者 char * 类型一起使用。

9.2.3 cmalloc.i

该模块定义了用于包装低级 C 内存分配函数malloc()、calloc()、 realloc()和free() 的宏。

%malloc(type [, name=type])

使用以下原型围绕malloc()创建一个包装器:

C++type *malloc_ name (int nbytes = sizeof( type ));

如果type为void,则需要大小参数 nbytes。的名称参数只需要一个包装型不是一个有效的标识符时要指定(例如,“ int * ”,“double ** ”,等等)。

%calloc(type [, name=type])

使用以下原型围绕 calloc() 创建一个包装器:

C++type *calloc_ name (int nobj =1, int sz = sizeof( type ));

如果 type 为 void,则必须输入大小参数 sz。

%realloc(type [, name=type])

使用以下原型围绕 realloc() 创建一个包装器:

C++type *realloc_name(type *ptr, int nitems);

注意:与 C realloc() 不同,此宏生成的包装器隐式包含相应类型的大小。例如,realloc_int(p, 100) 重新分配 p 使其包含 100 个整数。

%free(type [, name=type])

使用以下原型围绕free()创建一个包装器:

C++void free_ name ( type *ptr);

%sizeof(type [, name=type])

创建常量:

C++%constant int sizeof_ name = sizeof( type );

%allocators(type [, name=type])

为上述所有五个操作生成包装器。

下面是一个简单的例子,说明了这些宏的使用:

C++// SWIG 接口%module 示例%include "cmalloc.i" %malloc(int); %free(int); %malloc(int *, intp); %free(int *, intp); %allocators(double);

现在,在脚本中:

Python>>> from example import * >>> a = malloc_int() >>> a '_000efa70_p_int' >>> free_int(a) >>> b = malloc_intp() >>> b '_000efb20_p_p_int' >>> free_intp(b ) >>> c = calloc_double(50) >>> c '_000fab98_p_double' >>> c = realloc_double(100000) >>> free_double(c) >>> 打印 sizeof_double 8 >>>

9.2.4 cdata.i

所述 cdata.i 模块定义了从或者到原始 C 数据转换成目标语言串的函数。该模块的主要应用是二进制数据结构的打包/解包——例如,如果您需要从缓冲区中提取数据。目标语言必须支持带有嵌入二进制数据的字符串才能使其工作。

const char *cdata(void *ptr, size_t nbytes)

将 ptr 处的 nbytes 数据转换为字符串。 ptr 可以是任何指针。

void memmove(void *ptr, const char *s)

将 s 中的所有字符串数据复制到 ptr 指向的内存中。该字符串可能包含嵌入的 NULL 字节。这实际上是标准 C 库memmove函数的包装器,它被声明为 void memmove(void *ptr, const void *src, size_t n)。的 src 和长度 n 参数从底层包装代码提取语言特定字符串 s 。

这些函数的一种用途是从内存中打包和解包数据。这是一个简短的例子:

C++// SWIG 接口%module example%include "carrays.i" %include "cdata.i" %array_class(int, intArray);

Python 示例:

Python>>> a = intArray(10) >>> for i in range(0, 10): ... a[i] = i >>> b = cdata(a, 40) >>> b '\x00\ x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04 \x00\x00\x00\x05\x00\x05\x00\x00 x00\x06\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00\t' >>> c = intArray(10) >>> memmove(c, b) >>> print c[4] 4 >>>

由于数据的大小并不总是已知的,因此还定义了以下宏:

%cdata(type [, name=type])

生成以下函数以提取给定类型的 C 数据。

C++char *cdata_name(type* ptr, int nitems)

nitems 是要提取的给定类型的项目数。

注意:这些函数提供对内存的直接访问,并可用于覆盖数据。显然,它们是不安全的。

9.3 C 字符串处理

使用 C 程序时的一个常见问题是处理使用 char * 操作原始字符数据的函数。在某种程度上,问题的出现是因为对 char * 有不同的解释——它可能是以 NULL 结尾的字符串,也可能指向二进制数据。此外,操作原始字符串的函数可能会改变数据、执行隐式内存分配或使用固定大小的缓冲区。

使用char *的问题(和危险)是众所周知的。但是,SWIG 的职责不是强制执行道德。本节中的模块提供操作原始 C 字符串的基本功能。

9.3.1 默认字符串处理

假设你有一个带有这个原型的 C 函数:

C++char *foo(char *s);

此函数的默认包装行为是将 s 设置为原始 char *,该字符引用目标语言中的内部字符串数据。换句话说,如果你使用像 Tcl 这样的语言,并且你写了这个,

Python% foo Hello

那么 s 将指向 Tcl 解释器中“Hello”的表示。返回 char * 时,SWIG 假定它是一个以 NULL 结尾的字符串并制作它的副本。这为目标语言提供了自己的结果副本。

默认行为存在明显的问题。首先,由于一个 char* 参数指向目标语言内部的数据,它是修改这些数据的安全函数(这样做可能会损坏解释,并导致崩溃)。此外,默认行为不适用于二进制数据。相反,字符串被假定为以 NULL 结尾。

9.3.2 传递二进制数据

如果您有一个需要二进制数据的函数,

C++size_t parity(char *str, size_t len, size_t initial);

您可以使用类型映射将参数(char *str, size_t len)包装为单个参数。只需这样做:

C++%apply (char *STRING, size_t LENGTH) { (char *str, size_t len) }; ... size_t parity(char *str, size_t len, size_t initial);

现在,在目标语言中,您可以像这样使用二进制字符串数据:

Python>>> s = "H\x00\x15eg\x09\x20">>> parity(s, 0)

在包装函数中,传递的字符串将被扩展为指针和长度参数。该 (char STRING,int LENGTH) 的多参数类型映射也是除了可用 (char *STRING,size_t LENGTH) 以外的补充。

9.3.3 使用 %newobject 释放内存

如果你有一个像这样分配内存的函数,

C++char *foo() { char *result = (char *) malloc(...); ... return result;}

那么 SWIG 生成的包装器将发生内存泄漏——返回的数据将被复制到一个字符串对象中,而旧的内容将被忽略。

要修复内存泄漏,请使用%newobject指令。

C++%newobject foo; ... char *foo();

如果适当的目标语言支持可用,这将发布结果。SWIG 为 char * 提供了适当的“newfree”类型映射,以便释放内存,但是,您可能需要为其他类型提供自己的“newfree”类型映射。有关更多详细信息,请参阅对象所有权和 %newobject。

9.3.4 cstring.i

该 cstring.i 库文件提供宏的集合与函数处理,要么发生变异字符串参数或通过他们的论点试图输出字符串数据。这种函数的一个例子可能是这个相当有问题的实现:

C++void get_path(char *s) { // 潜在的缓冲区溢出---呃,哦。 sprintf(s, "%s/%s", base_directory, sub_directory); } ... // C 程序中的其他地方{ char path[1024]; ... get_path(path); ... }

(题外话:如果你的程序真的有这样的功能,你最好用涉及边界检查的更安全的替代方案替换它们)。

本模块中定义的宏都扩展为各种类型映射的组合。因此,相同的模式匹配规则和想法适用。

%cstring_bounded_output(parm, maxsize)

将参数 parm 转换为输出值。假设输出字符串以 NULL 结尾且小于 maxsize 个字符。下面是一个例子:

C++%cstring_bounded_output(char *path, 1024); ... void get_path(char *path);

在目标语言中:

Python>>> get_path() /home/beazley/packages/Foo/Bar >>>

在内部,包装函数分配一个请求大小的小缓冲区(在堆栈上)并将其作为指针值传递。然后将存储在缓冲区中的数据作为函数返回值返回。如果函数已经返回一个值,则返回值和输出字符串一起返回(多个返回值)。如果写入的字节数超过maxsize字节,您的程序将因缓冲区溢出而崩溃!

%cstring_chunk_output(parm, chunksize)

将参数parm转换为输出值。输出字符串始终 CHUNKSIZE 并可能包含二进制数据。下面是一个例子:

C++%cstring_chunk_output(char *packet, PACKETSIZE); ... void get_packet(char *packet);

在目标语言中:

Python>>> get_packet() '\xa9Y:\xf6\xd7\xe1\x87\xdbH;y\x97\x7f\xd3\x99\x14V\xec\x06\xea\xa2\x88' >>>

这个宏本质上与 %cstring_bounded_output 相同。唯一的区别是,结果总是 CHUNKSIZE字符。此外,结果可以包含二进制数据。如果写入的字节数超过 maxsize 字节,您的程序将因缓冲区溢出而崩溃!

%cstring_bounded_mutable(parm, maxsize)

将参数parm转换为可变字符串参数。假定输入字符串以 NULL 结尾且小于maxsize 个字符。还假定输出字符串以 NULL 结尾且少于maxsize 个字符。

C++%cstring_bounded_mutable(char *ustr, 1024); ... void make_upper(char *ustr);

在目标语言中:

Python>>> make_upper("hello world") 'hello world' >>>

在内部,这个宏几乎与 %cstring_bounded_output 完全相同。唯一的区别是该参数接受用于初始化内部缓冲区的输入值。需要强调的是,这个函数不会改变传递的字符串值——而是复制输入值,改变它,然后将其作为结果返回。如果写入的字节数超过maxsize字节,您的程序将因缓冲区溢出而崩溃!

%cstring_mutable(parm [, expansion])

将参数parm转换为可变字符串参数。假定输入字符串以 NULL 结尾。可选参数扩展指定了字符串在修改时可能增长的额外字符数。假设输出字符串以 NULL 结尾并且小于输入字符串加上任何扩展字符的大小。

C++%cstring_mutable(char *ustr); ... void make_upper(char *ustr); %cstring_mutable(char *hstr, HEADER_SIZE); ... void attach_header(char *hstr);

在目标语言中:

Python>>> make_upper("hello world") 'HELLO WORLD' >>> attach_header("Hello world") 'header: Hello world' >>>

这个宏与 %cstring_bounded_mutable() 的不同之处在于缓冲区是动态分配的(在堆上使用 malloc/new )。该缓冲区总是足够大,可以存储输入值的副本以及可能已请求的任何扩展字节。需要强调的是,这个函数不会直接改变传递的字符串值——而是复制输入值,改变它,然后将其作为结果返回。如果函数扩展结果超过扩展额外字节,那么程序将因缓冲区溢出而崩溃!

%cstring_output_maxsize(parm, maxparm)

此宏用于处理有界字符输出函数,其中提供了char *和最大长度参数。作为输入,用户只需提供最大长度。返回值假定为以 NULL 结尾的字符串。

C++%cstring_output_maxsize(char *path, int maxpath); ... void get_path(char *path, int maxpath);

在目标语言中:

Python>>> get_path(1024) '/home/beazley/Packages/Foo/Bar' >>>

该宏为需要将字符串数据写入缓冲区的函数提供了一种更安全的替代方法。用户提供的缓冲区大小用于在堆上动态分配内存。结果被放入该缓冲区并作为字符串对象返回。

%cstring_output_withsize(parm, maxparm)

该宏用于处理有界字符输出函数,其中传递了char *和指针int *。最初,int *参数指向一个包含最大大小的值。返回时,假定该值包含实际字节数。作为输入,用户只需提供最大长度。输出值是一个可能包含二进制数据的字符串。

C++%cstring_output_withsize(char *data, int *maxdata); ... void get_data(char *data, int *maxdata);

在目标语言中:

Python>>> get_data(1024) 'x627388912' >>> get_data(1024) 'xyzzy' >>>

这个宏是 %cstring_output_chunk() 的一个更强大的版本。内存是动态分配的,可以是任意大的。此外,函数可以通过更改 maxparm 参数的值来控制实际返回的数据量。

%cstring_output_allocate(parm, release)

这个宏用以返回在程序内部被分配内存的字符串,然后在一个char ** 类型的参数里返回其内容。例如:

C++void foo(char **s) { *s = (char *) malloc(64); sprintf(*s, "Hello world\n"); }

假定返回的字符串以 NULL 结尾。 release 指定如何释放分配的内存(如果适用)。下面是一个例子:

C++%cstring_output_allocate(char **s, free(*$1)); ... void foo(char **s);

在目标语言中:

Python>>> foo() 'Hello world\n' >>>

%cstring_output_allocate_size(parm, szparm,release)

此宏用于返回在程序中分配的字符串,并在char **和 int *类型的两个参数中返回。例如:

C++void foo(char **s, int *sz) { *s = (char *) malloc(64); *sz = 64; // 写入一些二进制数据 ... }

返回的字符串可能包含二进制数据。release指定如何释放分配的内存(如果适用)。下面是一个例子:

C++%cstring_output_allocate_size(char **s, int *slen, free(*$1)); ... void foo(char **s, int *slen);

在目标语言中:

Python>>> foo() '\xa9Y:\xf6\xd7\xe1\x87\xdbH;y\x97\x7f\xd3\x99\x14V\xec\x06\xea\xa2\x88' >>>

这是在 SWIG 中返回二进制字符串数据的最安全、最可靠的方法。如果您有符合另一个原型的函数,您可以考虑使用辅助函数包装它们。例如,如果你有这个:

C++char *get_data(int *len);

你可以用这样的函数包装它:

C++void my_get_data(char **result, int *len) { *result = get_data(len); }

评论:

• 对cstring.i模块的支持取决于目标语言。目前并非所有 SWIG 模块都支持此库。

• 可靠地处理原始 C 字符串是一个微妙的话题。在 SWIG 中有很多方法可以实现这一点。该库为一些常用技术提供支持。

• 如果在 C++ 中使用,该库使用new和delete [] 进行内存分配。如果使用 ANSI C,则库使用malloc() 和free()。

• 与其直接操作char *,不如考虑使用特殊的字符串结构或类。

9.4 STL/C++ 库

本节中的库模块提供对包括 STL 在内的标准 C++ 库部分的访问。SWIG 对 STL 的支持是一项持续的努力。对某些语言模块的支持非常全面,但一些较少使用的模块没有编写那么多的库代码。

下表显示了支持哪些 C++ 类以及 C++ 库的等效 SWIG 接口库文件。

C++类

C++ 库文件

SWIG 接口库文件

std::auto_ptr

memory

std_auto_ptr.i

std::deque

deque

std_deque.i

std::list

list

std_list.i

std::map

map

std_map.i

std:pair

utility

std_pair.i

std::set

set

std_set.i

std::string

string

std_string.i

std::vector

vector

std_vector.i

std::array

array (C++11)

std_array.i

std::shared_ptr

shared_ptr (C++11)

std_shared_ptr.i

这份清单绝不是完整的。一些语言模块支持上述的一个子集,一些支持额外的 STL 类。请在相应的语言库目录中查找库文件。

9.4.1 std::string

std_string.i 库提供了用于转换 C++ 的 std :: string 对象的 typemaps,和从目标的脚本语言的字符串到 std::string 对象的 typemaps。例如:

C++%module example%include "std_string.i" std::string foo(); void bar(const std::string &x);

在目标语言中:

Pythonx = foo(); # 返回一个字符串对象bar("Hello World"); # 将字符串作为 std::string 传递

人们遇到的一个常见问题是包含 std::string 的类/结构。这可以通过定义类型映射来克服。例如:

C++%module example%include "std_string.i" %apply const std::string& {std::string* foo}; struct my_struct { std::string foo; };

在目标语言中:

Pythonx = my_struct(); x.foo="Hello World"; # 用字符串赋值print x.foo; # 打印为字符串

该模块仅支持 std::string 和 const std::string & 类型。指针和非常量引用保持不变并作为 SWIG 指针返回。

该库文件完全了解 C++ 命名空间。如果导出 std::string 或使用 typedef 重命名它,请确保在接口中包含这些声明。例如:

C++%module example%include "std_string.i" using namespace std; typedef std::string String;... void foo(string s, const String &t); // std_string 类型映射仍然适用

9.4.2 std::vector

std_vector.i 库提供了用于 C ++ 支持的 STL std::vector 类。使用此库涉及使用 %template 指令。您需要做的就是为要使用的类型实例化不同版本的 vector。例如:

C++%module example%include "std_vector.i" namespace std { %template(vectori) vector<int>; %template(vectord) vector<double>; };

当模板 vector<X> 被实例化时,会发生很多事情:

• 在目标语言中创建了一个公开 C++ API 的类。这可以用于创建对象、调用方法等。这个类目前是真正的 STL 向量类的子集。

• 输入类型映射是为 vector<X>、const vector<X> &和const vector<X> * 定义的。对于这些中的每一个,可以传递一个指针 vector<X> * 或目标语言中的本机列表对象。

• 为 vector<X> 定义了一个输出类型映射。在这种情况下,向量中的值被扩展为目标语言的列表对象。

• 对于该类型的所有其他变体,包装器希望以通常的方式接收 vector<X> * 对象。

• 定义了 std::out_of_range 的异常处理程序。

• 可选地,可以定义用于索引、项目检索、切片和元素分配的特殊方法。这取决于目标语言。

为了说明这个库的使用,请考虑以下函数:

C++/* 文件:example.h */ #include <vector> #include <algorithm> #include <functional> #include <numeric> double average(std::vector<int> v) { return std::accumulate(v.begin(), v.end(), 0.0)/v.size(); } std::vector<double> half(const std::vector<double>& v) { std::vector<double> w(v); for (unsigned int i=0; i<w.size(); i++) w[i] /= 2.0; return w; } void halve_in_place(std::vector<double>& v) { std::transform(v.begin(), v.end(), v.begin(), std::bind2nd(std::divides<double> (), 2.0)); }

要使用 SWIG 包装,您可以编写以下内容:

C++%module example %{ #include "example.h" %} %include "std_vector.i" // 实例化 example 用到的模板namespace std { %template(IntVector) vector<int>; %template(DoubleVector) vector<double>; } // 包含具有上述原型的头文件%include "example.h"

现在,为了说明脚本解释器中的行为,请考虑以下 Python 示例:

Shell>>> from example import *>>> iv = IntVector(4) # Create an vector<int>>>> for i in range(0, 4):... iv[i] = i>>> average(iv) # Call method1.5>>> average([0, 1, 2, 3]) # Call with list1.5>>> half([1, 2, 3]) # Half a list(0.5, 1.0, 1.5)>>> halve_in_place([1, 2, 3]) # OopsTraceback (most recent call last): File "<stdin>", line 1, in ?TypeError: Type error. Expected _p_std__vectorTdouble_t>>> dv = DoubleVector(4)>>> for i in range(0, 4):... dv[i] = i>>> halve_in_place(dv) # Ok>>> for i in dv:... print i...0.00.51.01.5>>> dv[20] = 4.5Traceback (most recent call last): File "<stdin>", line 1, in ? File "example.py", line 81, in __setitem__ def __setitem__(*args): return apply(examplec.DoubleVector___setitem__, args)IndexError: vector index out of range>>>

这个库模块完全了解 C++ 命名空间。如果您使用具有其他名称的向量,请确保包含适当的 using 或 typedef 指令。例如:

C++include "std_vector.i" namespace std { %template(IntVector) vector<int>;} using namespace std;typedef std::vector Vector; void foo(vector<int> *x, const Vector &x);

注意:该模块利用了几个高级 SWIG 功能,包括模板化类型映射和模板部分特化。如果您尝试使用模板包装其他 C++ 代码,您可以查看 std_vector.i 中包含的代码。或者,如果你想让他们的头爆炸,你可以向他们展示代码。

注意:此模块是为所有 SWIG 目标语言定义的。然而,参数转换细节和暴露给解释器的公共 API 各不相同。

9.4.3 STL 异常

许多 STL 包装函数添加了参数检查,如果值无效,将抛出依赖于语言的错误/异常。经典的例子是数组边界检查。编写库包装器以在出现错误时抛出 C++ 异常。C++ 异常反过来被转换为目标语言的适当错误/异常。总的来说,这种处理不需要定制,但是,可以通过提供适当的“抛出”类型映射轻松实现定制。例如:

C++%module exapmle%include "std_vector.i" %typemap(throws) std::out_of_range { // 自定义异常处理程序} %template(VectInt) std::vector<int>;

例如,自定义异常处理程序可能会记录异常,然后将其转换为目标语言的特定错误/异常。

使用 STL 时,建议添加异常处理程序以捕获所有 STL 异常。所述%异常指令可以用于通过将下面的代码来被包装的任何其他方法或库之前:

C++%include "exception.i" %exception { try { $action } catch (const std::exception& e) { SWIG_exception(SWIG_RuntimeError, e.what()); } }

任何抛出的 STL 异常都将得到妥善处理,而不是导致崩溃。

9.4.4 shared_ptr 智能指针

一些目标语言支持处理 shared_ptr 引用计数智能指针。这个智能指针在标准 C++11 库中作为std::shared_ptr 可用。在完全标准化之前,它也在 TR1 中作为 std::tr1::shared_ptr 出现。还可以支持广泛使用的 boost::shared_ptr。

为了使用 std::shared_ptr,应该包括std_shared_ptr.i库文件:

C++%include <std_shared_ptr.i>

预标准 std::tr1::shared_ptr 可以通过在包含 std_shared_ptr.i 库文件之前包含以下宏来使用:

C++#define SWIG_SHARED_PTR_SUBNAMESPACE tr1 %include <std_shared_ptr.i>

为了使用boost::shared_ptr,应该包含 boost_shared_ptr.i 库文件:

C++%include <boost_shared_ptr.i>

您一次只能在接口文件中使用 shared_ptr 的这些变体之一。并且所有三个变体都必须与 %shared_ptr(T) 宏结合使用,其中T是基础指针类型,等同于使用 shared_ptr<T>。类型 T 必须是非原始的。一个简单的例子演示了用法:

C++%module example%include <boost_shared_ptr.i> %shared_ptr(IntValue) %inline %{ #include <boost/shared_ptr.hpp> struct IntValue { int value; IntValue(int v) : value(v) {} }; static int extractValue(const IntValue &t) { return t.value; } static int extractValueSmart(boost::shared_ptr<IntValue> t) { return t->value; } %}

请注意,%shared_ptr(IntValue) 声明发生在包含提供宏的 boost_shared_ptr.i 库之后,非常重要的是,发生在 IntValue 类型的任何使用或声明之前。在%shared_ptr 的宏提供,有几件事情都处理这个智能指针,但大多是一些 typemaps 的。这些类型映射覆盖默认类型映射,以便存储底层代理类并将其作为指向 shared_ptr 的指针传递而不是指向基础类型的普通指针。这种方法意味着该类型的任何实例化都可以通过值、引用、指针或作为智能指针传递给采用该类型的方法。感兴趣的读者可能想查看生成的代码,但是,用法很简单,不需要与目标语言不同的处理方式。例如,上述 Java 代码的一个简单用例是:

JavaIntValue iv = new IntValue(1234); int val1 = example.extractValue(iv); int val2 = example.extractValueSmart(iv); System.out.println(val1 + " " + val2);

这个 shared_ptr 库的工作方式与 SWIG 的正常但有些有限的智能指针处理完全不同。除了代理类之外,shared_ptr 库不会生成额外的包装器,仅用于智能指针处理。包含继承关系的普通代理类照常生成。%shared_ptr 宏引入的唯一真正变化是代理类存储了一个指向 shared_ptr 实例的指针,而不是一个指向该实例的原始指针。从一个被 shared_ptr 包装的基派生的代理类也可以并且必须被包装为一个 shared_ptr。换句话说,继承层次结构中的所有类都必须与 %shared_ptr 一起使用宏。例如,以下代码可以与前面显示的基类一起使用:

C++%shared_ptr(DerivedIntValue) %inline %{ struct DerivedIntValue : IntValue { DerivedIntValue(int value) : IntValue(value) {} ... }; %}

现在可以将派生类的 shared_ptr 传递给目标语言中需要基类的方法,就像在 C++ 中一样:

C++DerivedIntValue div = new DerivedIntValue(5678); int val3 = example.extractValue(div); int val4 = example.extractValueSmart(div);

如果继承层次结构中的任何类省略了 %shared_ptr 宏,SWIG 将对此发出警告,并且生成的代码可能会也可能不会导致 C++ 编译错误。例如,以下输入:

C++%include "boost_shared_ptr.i" %shared_ptr(Parent); %inline %{ #include <boost/shared_ptr.hpp> struct GrandParent { virtual ~GrandParent() {} }; struct Parent : GrandParent { virtual ~Parent() {} }; struct Child : Parent { virtual ~Child() {} }; %}

警告丢失的智能指针信息:

Plain Textexample.i:12: Warning 520: Base class 'GrandParent' of 'Parent' is not similarly marked as a smart pointer.example.i:16: Warning 520: Derived class 'Child' of 'Parent' is not similarly marked as a smart pointer.

添加缺少的 %shared_ptr 宏将解决此问题:

C++%include "boost_shared_ptr.i" %shared_ptr(GrandParent); %shared_ptr(Parent); %shared_ptr(Child); ... as before ...

注意:对 %shared_ptr 和 director feature 的支持有些有限,完成的程度因不同的目标语言而异。请通过提供具有改进功能的补丁来帮助改进此支持。

9.4.5 auto_ptr 智能指针

尽管std::auto_ptr 在 C++11 中已弃用,但一些现有代码可能仍在使用它,因此 SWIG 为此类提供有限的支持:std_auto_ptr.i 定义了适用于返回此类型对象的函数的类型映射。不直接支持 std_auto_ptr.i的任何其他用途。

一个典型的使用示例是

C++%include <std_auto_ptr.i> %auto_ptr(Klass) %inline %{ class Klass { public: // 创建这个类的对象的工厂函数: static std::auto_ptr<Klass> Create(int value) { return std::auto_ptr <Klass>(新的Klass(value)); } int getValue() const { return m_value; } private: DerivedIntValue(int value) : m_value(value) {} int m_value; }; %}

返回的对象可以自然地从目标语言使用,例如从 C#:

C#Klass k = Klass.Create(17); int value = k.getValue();

9.5 实用程序库

9.5.1 exception.i

该 exception.i 库提供了一个独立于语言的功能在目标语言养运行时异常。该库主要由 SWIG 库编写者使用。如果可能,请使用目标语言可用的错误处理方案,因为在可以抛出哪些错误/异常方面具有更大的灵活性。

SWIG_exception(int code,const char *message)

在目标语言中引发异常。code是以下符号常量之一:

C++SWIG_MemoryError SWIG_IOError SWIG_RuntimeError SWIG_IndexError SWIG_TypeError SWIG_DivisionByZero SWIG_OverflowError SWIG_SyntaxError SWIG_ValueError SWIG_SystemError

message 是一个字符串,表示有关问题的更多信息。

该模块的主要用途是编写与语言无关的异常处理程序。例如:

C++%include "exception.i" %exception std::vector::getitem { try { $action } catch (std::out_of_range & e) { SWIG_exception(SWIG_IndexError, const_cast<char*>(e.what())); } }

10 参数处理

在第 3 章中,描述了 SWIG 对基本数据类型和指针的处理。特别是对 int 和 double 等原始类型映射到目标语言中的相应类型。对于其他一切,指针用于引用结构、类、数组和其他用户定义的数据类型。但是,在某些应用程序中,需要更改 SWIG 对特定数据类型的处理。例如,您可能希望通过函数的参数返回多个值。本章介绍了执行此操作的一些技术。

10.1 typemaps.i 库

本节介绍 typemaps.i 库文件——通常用于更改参数转换的某些属性。

10.1.1 简介

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

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

从阅读源代码可以看出,该函数在 double *result 参数中存储了一个值。但是,由于 SWIG 不检查函数体,因此无法知道这是底层行为。

处理这个问题的一种方法是使用 typemaps.i 库文件并编写如下接口代码:

C++// 使用类型映射的简单示例%module example %include "typemaps.i" %apply double *OUTPUT { double *result }; %inline %{ extern void add(double a, double b, double *result); %}

在%apply 指令告诉 SWIG,你将要用一个特殊类型的处理规则适用于一个类型。“ double *OUTPUT ” 规范定义了一个名称,此名称定义了描述如何从 double * 类型的参数返回输出值的规则。此规则适用于花括号中列出的所有数据类型——在本例中为“ double *result ”。

创建生成的模块后,您现在可以使用这样的函数(针对 Python 显示):

Python>>> a = add(3, 4) >>> print a7 >>>

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

一旦一个类型映射被应用到一个类型上,它对所有未来出现的类型和名称都有效。例如,您可以编写以下内容:

C++%module example%include "typemaps.i" %apply double *OUTPUT { double *result }; %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); extern void div(double a, double b, double *result); %} ...

在这种情况下,double *OUTPUT 规则适用于后面的所有函数。

类型映射转换甚至可以扩展到多个返回值。例如,考虑以下代码:

C++%include "typemaps.i" %apply int *OUTPUT { int *width, int *height }; // 返回一对 (width, height) void getwinsize(int winid, int *width, int *height);

在这种情况下,该函数返回多个值,允许像这样使用它:

Lua>>> w, h = genwinsize(wid) >>> print w 400 >>> print h 300 >>>

还应该注意的是,虽然 %apply 指令用于将类型映射规则与数据类型相关联,但您也可以直接在参数中使用规则名称。例如,你可以这样写:

C++// 使用类型映射的简单示例%module example %include "typemaps.i" %{ extern void add(double a, double b, double *OUTPUT); %} extern void add(double a, double b, double *OUTPUT);

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

C++%clear double *result; // 删除 double *result 的所有类型映射

10.1.2 输入参数

以下类型映射指示 SWIG 指针实际上仅包含单个输入值:

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

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

C++double add(double *a, double *b) { return *a+*b; }

现在,考虑这个 SWIG 接口:

C++%module example%include "typemaps.i" ... %{ extern double add(double *, double *); %} extern double add(double *INPUT, double *INPUT);

当该函数在脚本语言解释器中使用时,它会像这样工作:

Pythonresult = add(3, 4)

10.1.3 输出参数

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

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

可以如前面的示例中所示使用这些方法。例如,如果你有这个 C 函数:

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

SWIG 接口文件可能如下所示:

C++%module example%include "typemaps.i" ... %inline %{ extern void add(double a, double b, double *OUTPUT); %}

在这种情况下,只返回一个输出值,但这不是限制。通过将输出规则应用于多个参数(如前所示),可以返回任意数量的输出值。

如果函数还返回一个值,则它与参数一起返回。例如,如果你有这个:

C++extern int foo(double a, double b, double *OUTPUT);

该函数将返回两个值,如下所示:

Luairesult, dresult = foo(3.5, 2)

10.1.4 输入/输出参数

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

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

一个使用它的 C 函数可能是这样的:

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

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

C++%module example%include "typemaps.i" ... %{ extern void negate(double *); %} extern void negate(double *INOUT);

现在在脚本中,您可以简单地正常调用函数:

Pythona = negate(3); # a = -3 调用后

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

兼容性注意:该 INOUT 使用规则被称为 BOTH 在早期版本痛饮。保留向后兼容性,但已弃用。

10.1.5 使用不同的名称

如前所述,%apply 指令可用于将 INPUT、OUTPUT 和 INOUT 类型映射应用于不同的参数名称。例如:

C++// 使 double *result 成为输出值%apply double *OUTPUT { double *result }; // 使 Int32 *in 成为输入值%apply int *INPUT { Int32 *in }; // 使 long *x inout %apply long *INOUT {long *x};

要清除规则,请使用%clear指令:

C++%clear double *result; %clear Int32 *in, long *x;

类型映射声明是词法范围的,因此类型映射从定义点到文件末尾或匹配的 %clear 声明生效。

10.2 对输入值应用约束

除了改变对各种输入值的处理之外,还可以使用类型映射来应用约束。例如,您可能想确保一个值是正数,或者一个指针是非 NULL。这可以通过 constraints.i 库文件来完成。

10.2.1 简单约束示例

以下接口文件最好地说明了约束库:

C++// 带有约束的接口文件%module example %include "constraints.i" double exp(double x); double log(double POSITIVE); // 只允许正值double sqrt(double NONNEGATIVE); // 非负值只有double inv(double NONZERO); // 非零值void free(void *NONNULL); // 仅非空指针

此文件的行为与您预期的完全一样。如果任何参数违反约束条件,将引发脚本语言异常。因此,可以捕获错误的值,防止神秘的程序崩溃等。

10.2.2 约束方法

以下约束目前可用

Plain TextPOSITIVE Any number > 0 (not zero)NEGATIVE Any number < 0 (not zero)NONNEGATIVE Any number >= 0NONPOSITIVE Any number <= 0NONZERO Nonzero numberNONNULL Non-NULL pointer (pointers only).

10.2.3 对新数据类型应用约束

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

C++// 对 Real 变量应用约束%apply Number POSITIVE { Real in }; // 对指针类型应用约束%apply Pointer NONNULL { Vector * };

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

C++%clear Real in;%clear Vector *;

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-08-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 韩大 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档