SWIG 3 中文手冊——13. 約定

13 約定

A common problem that arises when wrapping C libraries is that of maintaining reliability and checking for errors. The fact of the matter is that many C programs are notorious for not providing error checks. Not only that, when you expose the internals of an application as a library, it often becomes possible to crash it simply by providing bad inputs or using it in a way that wasn't intended.python

This chapter describes SWIG's support for software contracts. In the context of SWIG, a contract can be viewed as a runtime constraint that is attached to a declaration. For example, you can easily attach argument checking rules, check the output values of a function and more. When one of the rules is violated by a script, a runtime exception is generated rather than having the program continue to execute.c++

包裝 C 庫時出現的常見問題是維護可靠性和檢查錯誤。事實是,許多 C 程序因不提供錯誤檢查而臭名昭著。不只如此,當你將應用程序的內部結構公開爲一個庫時,一般可能會因提供錯誤的輸入或以非預期的方式使用它而使其崩潰。express

本章介紹 SWIG 對軟件約定的支持。在 SWIG 的上下文中,約定能夠視爲附加到聲明的運行時約束。例如,你能夠輕鬆地附加參數檢查規則,檢查函數的輸出值等等。若是腳本違反了其中一個規則,則會生成運行時異常,而不是讓程序繼續執行。app

13.1 %contract 指令

Contracts are added to a declaration using the %contract directive. Here is a simple example:ide

使用 %contract 指令將約定添加到聲明中。這是一個簡單的示例:函數

%contract sqrt(double x) {
require:
  x >= 0;
ensure:
  sqrt >= 0;
}

...
double sqrt(double);

In this case, a contract is being added to the sqrt() function. The %contract directive must always appear before the declaration in question. Within the contract there are two sections, both of which are optional. The require: section specifies conditions that must hold before the function is called. Typically, this is used to check argument values. The ensure: section specifies conditions that must hold after the function is called. This is often used to check return values or the state of the program. In both cases, the conditions that must hold must be specified as boolean expressions.工具

In the above example, we're simply making sure that sqrt() returns a non-negative number (if it didn't, then it would be broken in some way).ui

Once a contract has been specified, it modifies the behavior of the resulting module. For example:this

在這種狀況下,將約定添加到 sqrt() 函數中。%contract 指令必須始終出如今相關聲明以前。約定中有兩個部分,二者都是可選的。require: 部分指定了在調用函數以前必須知足的條件。一般,它用於檢查參數值。ensure: 部分指定了在調用函數後必須知足的條件。一般用於檢查返回值或程序狀態。在這兩種狀況下,必須知足的條件都必須指定爲布爾表達式。code

在上面的示例中,咱們只是確保 sqrt() 返回一個非負數(若是不返回,那麼它將以某種方式被破壞)。

指定約定後,它將修改結果模塊的行爲。例如:

>>> example.sqrt(2)
1.4142135623730951
>>> example.sqrt(-2)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
RuntimeError: Contract violation: require: (arg1>=0)
>>>

13.2 %contract 與類

The %contract directive can also be applied to class methods and constructors. For example:

%contract 指令也能夠應用於類方法和構造函數。例如:

%contract Foo::bar(int x, int y) {
require:
  x > 0;
ensure:
  bar > 0;
}

%contract Foo::Foo(int a) {
require:
  a > 0;
}

class Foo {
public:
  Foo(int);
  int bar(int, int);
};

The way in which %contract is applied is exactly the same as the %feature directive. Thus, any contract that you specified for a base class will also be attached to inherited methods. For example:

%contract 的應用方式與 %feature 指令徹底相同。所以,你爲基類指定的任何約定也將附加到繼承的方法。例如:

class Spam : public Foo {
public:
  int bar(int, int);    // Gets contract defined for Foo::bar(int, int)
};

In addition to this, separate contracts can be applied to both the base class and a derived class. For example:

除此以外,能夠將單獨的約定同時應用於基類和派生類。例如:

%contract Foo::bar(int x, int) {
require:
  x > 0;
}

%contract Spam::bar(int, int y) {
require:
  y > 0;
}

class Foo {
public:
  int bar(int, int);   // Gets Foo::bar contract.
};

class Spam : public Foo {
public:
  int bar(int, int);   // Gets Foo::bar and Spam::bar contract
};

When more than one contract is applied, the conditions specified in a "require:" section are combined together using a logical-AND operation. In other words conditions specified for the base class and conditions specified for the derived class all must hold. In the above example, this means that both the arguments to Spam::bar must be positive.

當應用多個約定時,使用邏輯與運算將 require: 部分中指定的條件組合在一塊兒。換句話說,爲基類指定的條件和爲派生類指定的條件都必須成立。在上面的示例中,這意味着 Spam::bar 的兩個參數都必須爲正。

13.3 約定集成與 %aggregate_check

Consider an interface file that contains the following code:

考慮包含如下代碼的接口文件:

#define  UP     1
#define  DOWN   2
#define  RIGHT  3
#define  LEFT   4

void move(SomeObject *, int direction, int distance);

One thing you might want to do is impose a constraint on the direction parameter to make sure it's one of a few accepted values. To do that, SWIG provides an easy to use macro %aggregate_check() that works like this:

你可能想作的一件事是對 direction 參數施加約束,以確保它是少數幾個可接受的值之一。爲此,SWIG 提供了一個易於使用的宏 %aggregate_check(),其工做方式以下:

%aggregate_check(int, check_direction, UP, DOWN, LEFT, RIGHT);

This merely defines a utility function of the form

只是定義一個工具函數

int check_direction(int x);

That checks the argument x to see if it is one of the values listed. This utility function can be used in contracts. For example:

這將檢查參數 x 以查看其是否爲列出的值之一。能夠在約定中使用此實用程序函數。例如:

%aggregate_check(int, check_direction, UP, DOWN, RIGHT, LEFT);

%contract move(SomeObject *, int direction, in) {
require:
  check_direction(direction);
}

#define  UP     1
#define  DOWN   2
#define  RIGHT  3
#define  LEFT   4

void move(SomeObject *, int direction, int distance);

Alternatively, it can be used in typemaps and other directives. For example:

另外,它能夠在類型映射和其餘指令中使用。例如:

%aggregate_check(int, check_direction, UP, DOWN, RIGHT, LEFT);

%typemap(check) int direction {
  if (!check_direction($1)) SWIG_exception(SWIG_ValueError, "Bad direction");
}

#define  UP     1
#define  DOWN   2
#define  RIGHT  3
#define  LEFT   4

void move(SomeObject *, int direction, int distance);

Regrettably, there is no automatic way to perform similar checks with enums values. Maybe in a future release.

遺憾的是,沒有自動的方法能夠對枚舉值執行相似的檢查。也許在未來的版本中能夠。

13.4 注意事項

Contract support was implemented by Songyan (Tiger) Feng and first appeared in SWIG-1.3.20.

約定支持由 Songyan(Tiger)Feng 實施,並首次出如今 SWIG-1.3.20 中。