5.7 接口创建策略
本节描述使用SWIG创建接口文件的通用方法。语言特定的部分请参考后面的章节。
5.7.1 为SWIG准备C程序
SWIG不需要修改你的C代码,但是如果你塞给他一组原始的C语言头文件或源代码,结果可能不像你预期的那样工作——事实上,结果可能很糟糕。下面是当包装C程序成接口时,你需要遵循的步骤:
- 标示出你要包装的函数。一个C程序中,可能不必要访问所有的功能——因此,事先规划能大大简化脚本语言的接口。C的头文件时决定包装什么功能的很好起点。
- 创建一个新的接口文件,给脚本语言描述你的接口。
- 拷贝合适的声明到接口文件中,或使用SWIG的
%include
指令处理C头或源文件。 - 确保接口文件中的所有表示都使用ANSI C/C++语法。
- 确保接口文件中包含素所有必要的
typedef
声明和类型信息。特别是,要确保类型信息以C/C++编译器要求的合适的顺序书写。更重要的是,在使用类型前一定要定义它!如果类型信息确实需要而你又没有提供的话,C编译器会告诉你的,而SWIG却不会警告或提示错误,因为它被设计成无需全的类型信息也可以工作。但是,如果类型信息没有正确指定,包装可能不是最优的,并且可能导致不能编译的C/C++代码。 - 如果你的程序有
main()
函数,你需要重命名它(接着往下看)。 - 运行SWIG并编译。
尽管这些看起来挺复杂的,但当你真的使用起来后会发现其实挺简单的。
创建接口的过程中,SWIG可能或遇到语法错误或其他问题。处理这些问题的最好方式是,简单拷贝这些代码到独立的接口文件中,然后编辑它。不管怎样,SWIG的开发者们已经尽了很大努力的去改进SWIG解释器了——如果发现问题,你应该报告解释错误到swig-devel mailing list或SWIG bug tracker。
5.7.2 SWIG接口文件
使用SWIG最好的方式生成一个独立的接口文件。假设你有如下的C代码:
/* File : header.h */
#include <stdio.h>
#include <math.h>
extern int foo(double);
extern double bar(int, int);
extern void dump(FILE *f);
针对这个头文件的典型SWIG接口文件可能长成下面这样:
/* File : interface.i */
%module mymodule
%{
#include "header.h"
%}
extern int foo(double);
extern double bar(int, int);
extern void dump(FILE *f);
当然,这种情况下,我们的头文件比较简单,可以在接口文件中用另外一种更简单的方式:
/* File : interface.i */
%module mymodule
%{
#include "header.h"
%}
%include "header.h"
这种方法的主要优点就是将来头文件改变后,可以减少对接口文件的维护。在一些非常复杂的项目中,一个接口文件包含了大量的%include
指令和#inclue
语句,这是降低维护成本的一种非常普遍的设计方式。
5.7.3 为什么要使用独立的接口文件呢?
尽管SWIG可以解释多数的头文件,但通常的做法是写一个独立的.i接口文件,用于定义一个包或模块的接口。有几个原因支持这么去做:
- 很好有必要访问一个大的软件中的每个独立功能。多数的C函数可能在脚本环境中根本就没什么用处。因此,为什么还要去包装它们呢?
- 独立的接口文件可以提供更精确的控制准则,它知道这些接口应该如何去构建。
- 接口文件可以提供更结构化和组织化的管理。
- SWIG不能解释出现在头文件中的某些定义。使用独立的文件允许你将这些问题隔离开来。
- 接口文件对提供什么样的接口提供更精确的控制。像扩展系统的用户能查看接口文件,并且立刻了解哪些功能是可用的,而不需要再深入到原始头文件中去了。
5.7.4 获取正确的头文件
有时候,需要使用默写头文件让SWIG生成的代码得以编译通过。确保你使用%{...%}
包含了某些头文件:
%module graphics
%{
#include <GL/gl.h>
#include <GL/glu.h>
%}
// Put the rest of the declarations here
...
5.7.5 如何处理main()函数
如果你的程序定义了main()
函数,你就需要删除或重命名它以便在脚本语言中使用。多数的脚本语言定义了它们自己的main()
过程来代替你提供的。main()
对动态加载的模块也没什么用处。有几种方式可用于解决main()
函数冲突:
- 将其全部删除。
- 重命名
main()
为其他名字。你可以这样编译你的C程序:-Dmain=oldmain
。 - 使用条件编译指令,使其只有在不使用脚本语言绑定是才编译。
删除main()
可能会导致程序初始化问题。为解决这个问题,你可以考虑编写一个特殊的program_init()
来在启动时初始化你的程序。这个函数可以在脚本语言中作为第一个操作调用,或者当SWIG生成的模块被加载时调用。
一般C程序的main()
函数主要用来解释命令行选项并设置程序内部使用的参数。但是,通过使用脚本语言,你可能会创建更具交互性的程序。多数情况下,老的main()
可以完全用Perl、Python或Tcl的脚本替换。
某些情况下,你可能倾向于为
main()
函数创建脚本绑定。如果你这样做了,编译可能也能工作,模块也可能会正确加载。唯一的问题是,当你调用包装的main()
函数是,会发现它实际上调用了脚本语言解析器的main函数了!这是动态连接器符号绑定机制的副作用。最终提示:不要这样做!!!