二、SWIG 之 包装简单的C声明

一、处理基本类型

1.1 处理整数(int, bool...)

为了构建接口,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 类型。如果该值太大而无法匹配,则会被默默截断。

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

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

1.2 处理浮点数(float,double)

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

1.3 处理字符(char)

        unsigned char 和 signed char 是特殊情况,以 8 位整数处理。通常,char 数据类型被映射为单字符 ASCII 字符串。除非目标语言提供特殊的布尔类型,否则 bool 数据类型将转换为 0 和 1。

        char 数据类型映射到带有 NULL 终止符的单字符 ASCII 字符串。在脚本语言中使用时,它显示为包含字符值的小字符串。将值转换回 C 时,SWIG 从脚本语言中获取一个字符串,并将第一个字符作为字符值剥离。因此,如果将值 foo 分配给 char 数据类型,则它将获得值 f。

        char * 数据类型作为以 NULL 结尾的 ASCII 字符串处理。SWIG 将此映射为目标脚本语言中的 8 位字符串。SWIG 将目标语言中的字符串转换为 NULL 结尾的字符串,然后再将它们传递给 C/C++ 。这些字符串的默认处理不允许它们具有嵌入的 NULL 字节。因此,char * 数据类型通常不适合传递二进制数据。但是,可以通过定义 SWIG 类型映射来更改此行为。后面的类型映射会具体描述。

        目前,SWIG 对 Unicode 和宽字符串(C wchar_t 类型)提供有限的支持。有些语言为 wchar_t 提供了类型映射,但这些语言可能无法在不同的操作系统中移植。对于那些提供 Unicode 支持的脚本语言,Unicode 字符串通常以 8 位表示形式提供,例如 UTF-8,可以映射到 char * 类型(在这种情况下,SWIG 接口可能会起作用)。如果要包装的程序使用 Unicode,则无法保证目标语言中的 Unicode 字符将使用相同的内部表示(例如,UCS-2 与 UCS-4)你可能需要编写一些特殊的转换函数。

二、全局变量

只要有可能,SWIG 就会将 C/C++ 全局变量映射到脚本语言变量中, 变量可以不用在C语言中声明也可以。

/* example.i */
%module example
%{
double foo;
%}
double foo;
# script.py
import example
example.cvar.foo = 3.14
print(example.cvar.foo)  # 3.14

        每当使用脚本语言变量时,都会访问底层 C 全局变量。虽然 SWIG 尽一切努力使全局变量像脚本语言变量一样工作,但并不总是这样做。例如,在 Python 中,必须通过称为 cvar 的特殊变量对象(如上所示)访问所有全局变量。在 Ruby 中,变量作为模块的属性进行访问。其他语言可以将变量转换为一对存取函数。例如,Java 模块生成一对函数 double get_foo() 和 set_foo(double val) 用于操作该值。

        最后,如果全局变量已声明为 const,则它仅支持只读访问。

三、常量

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

/* example.i */
%module example

%{
enum boolean {NO=0, YES=1};
enum months {JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG,
             SEP, OCT, NOV, DEC};
%}

#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
# script.py

import example

print(example.I_CONST) # 5
print(example.PI) # 3.14159
print(example.S_CONST) # hello world
print(example.JAN) # 0
print(example.FLAGS) # 76

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

#define EXTERN extern
EXTERN void foo();
/*在这种情况下,可能不希望创建一个名为 EXTERN 的常量(值是什么?)。通常,SWIG 不会为宏创建常量,除非该值可以由预处理器完全确定。
比如: 上面的 #define PI_4 PI/4 定义一个常量,因为 PI 已被定义为常量且值已知。但是,出于同样的保守原因,即使是简单强制转换的常量也会被忽略,例如:
*/ #define F_CONST (double) 5 // 带强制转换的浮点常量

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

        对于枚举,将原始枚举定义包含在接口文件中的某个位置(在头文件或 %{ %} 块中)至关重要。SWIG 仅将枚举转换为向脚本语言添加常量所需的代码。它需要原始的枚举声明才能获得 C 编译器指定的正确枚举值。

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

四、const的使用

        C 编程的一个常见疑惑是声明中 const 限定符的语义含义——特别是当它与指针和其他类型修饰符混合时。所有变量声明,无论怎样使用 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
// 在这种情况下,指针 e 可以改变——只是它指向的值是只读的。

        注意: 对于函数中使用的 const 参数或返回类型,SWIG 几乎忽略了这些是 const 的事实。

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

五、char * 的注意事项

当字符串从脚本语言传递到 C char * 时,指针通常指向存储在解释器中的字符串数据。修改这些数据几乎总是一个坏主意。此外,某些语言可能明确禁止它。例如,在 Python 中,字符串应该是不可变的。
问题的主要来源是可能会修改字符串数据的函数。一个典型的例子是这样的函数:

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

虽然 SWIG 肯定会为此生成一个包装器,但它的行为将是未定义的。实际上,它可能会导致应用程序崩溃,导致分段错误或其他与内存相关的问题。这是因为 s 指向的是目标语言中的一些内部数据——不应该碰的数据。

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

 

posted on 2022-11-19 15:20  软饭攻城狮  阅读(105)  评论(0编辑  收藏  举报

导航