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 listSWIG bug tracker

5.7.2 SWIG接口文件

使用SWIG最好的方式生成一个独立的接口文件。假设你有如下的C代码:

  1. /* File : header.h */
  2. #include <stdio.h>
  3. #include <math.h>
  4. extern int foo(double);
  5. extern double bar(int, int);
  6. extern void dump(FILE *f);

针对这个头文件的典型SWIG接口文件可能长成下面这样:

  1. /* File : interface.i */
  2. %module mymodule
  3. %{
  4. #include "header.h"
  5. %}
  6. extern int foo(double);
  7. extern double bar(int, int);
  8. extern void dump(FILE *f);

当然,这种情况下,我们的头文件比较简单,可以在接口文件中用另外一种更简单的方式:

  1. /* File : interface.i */
  2. %module mymodule
  3. %{
  4. #include "header.h"
  5. %}
  6. %include "header.h"

这种方法的主要优点就是将来头文件改变后,可以减少对接口文件的维护。在一些非常复杂的项目中,一个接口文件包含了大量的%include指令和#inclue语句,这是降低维护成本的一种非常普遍的设计方式。

5.7.3 为什么要使用独立的接口文件呢?

尽管SWIG可以解释多数的头文件,但通常的做法是写一个独立的.i接口文件,用于定义一个包或模块的接口。有几个原因支持这么去做:

  • 很好有必要访问一个大的软件中的每个独立功能。多数的C函数可能在脚本环境中根本就没什么用处。因此,为什么还要去包装它们呢?
  • 独立的接口文件可以提供更精确的控制准则,它知道这些接口应该如何去构建。
  • 接口文件可以提供更结构化和组织化的管理。
  • SWIG不能解释出现在头文件中的某些定义。使用独立的文件允许你将这些问题隔离开来。
  • 接口文件对提供什么样的接口提供更精确的控制。像扩展系统的用户能查看接口文件,并且立刻了解哪些功能是可用的,而不需要再深入到原始头文件中去了。

5.7.4 获取正确的头文件

有时候,需要使用默写头文件让SWIG生成的代码得以编译通过。确保你使用%{...%}包含了某些头文件:

  1. %module graphics
  2. %{
  3. #include <GL/gl.h>
  4. #include <GL/glu.h>
  5. %}
  6. // Put the rest of the declarations here
  7. ...

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函数了!这是动态连接器符号绑定机制的副作用。

最终提示:不要这样做!!!