C
和C++
作为创建高性能代码的首选平台而被广泛认可(正确的是)。 对开发人员的一个常见要求是他们从脚本语言界面公开C/C++
代码,这是简化包装和界面生成器(SWIG)出现的地方。SWIG使您可以将C/C++
代码广泛公开脚本语言,包括Ruby,Perl,Tcl和Python。 本文使用Ruby作为公开C/C++
功能的脚本接口。 要继续阅读本文,您应该具有C/C++
和Ruby的中级知识。
SWIG在以下几种情况下是一个很好的工具,包括:
- 提供
C/C++
代码的脚本接口,使用户更轻松 - 向您的Ruby代码添加扩展或用高性能替代品替换现有模块
- 使用脚本环境提供对代码进行单元和集成测试的能力
- 在TK中开发图形用户界面并将其与
C/C++
后端集成
另外,与每次启动GNU调试器相比,SWIG调试起来容易得多。
带有SWIG的Hello World
作为输入,SWIG需要一个包含ANSI C/C++
声明和SWIG指令的文件。 我将此输入文件称为SWIG接口文件。 重要的是要记住,SWIG只需要生成包装程序代码所需的尽可能多的信息。 该接口文件通常具有* .i或* .swg扩展名。 这是第一个扩展文件test.i:
%module test
%constant char* Text = "Hello World with SWIG"
使用SWIG运行以下代码:
swig –ruby test.i
第二个片段中的命令行在当前文件夹中生成一个名为test_wrap.c的文件。 现在,您需要根据此C
文件创建一个共享库。 这是命令行:
bash$ gcc –fPIC –c test_wrap.c –I$RUBY_INCLUDE
bash$ gcc –shared test_wrap.o –o test_wrap.so –lruby –L$RUBY_LIB
而已。 一切require 'test_wrap'
,因此只需启动交互式Ruby Shell(IRB),然后输入require 'test_wrap'
即可检出Ruby Test
模块及其内容。 这是扩展的Ruby方面:
irb(main):001:0> require 'test_wrap'
=> true
irb(main):002:0> Test.constants
=> ["Text"]
irb(main):003:0> Test:: Text
=> "Hello World with SWIG"
SWIG可用于生成各种语言扩展,只需运行swig –help
即可查看所有可用选项。 对于Ruby,输入swig –ruby <interface file>
; 对于Perl,请使用swig –perl <interface file>
。
您也可以使用SWIG生成C++
代码:您只需在命令行中使用–c++
。 在上一个示例中,运行swig –c++ –ruby test.i
将在当前文件夹中生成一个名为test_wrap.cxx的文件。
SWIG基础知识
SWIG接口文件语法是C
的超集。 实际上,SWIG通过自定义C
预处理器处理其输入文件。 另外,通过特殊指令( %module
, %constant
等)控制接口文件中的SWIG操作,这些指令后跟百分号( %
)。 SWIG接口还允许您定义以%{
开头并以%}
结尾的块。 %{
和%}
之间的所有内容都被逐字复制到生成的包装文件中。
SWIG接口文件必须以%module
声明开头-例如, %module module-name
,其中module-name是目标语言扩展模块的名称。 如果目标语言是Ruby,则类似于创建Ruby模块。 通过提供命令行选项–module module-name-modified
可以覆盖模块名称:在这种情况下,目标语言模块名称是(您猜对了) module-name-modified。 现在,让我们继续讲常量。
模块初始化功能
SWIG具有%init
特殊指令,用于定义模块初始化功能。 %init
的%{ … %}
块内定义的代码是模块加载时调用的代码。 这是代码:
%module test
%constant char* Text = “Hello World with SWIG”
%init %{
printf(“Initialization etc. gets done here\n”);
%}
现在,重新启动IRB。 加载模块后,您将获得以下信息:
irb(main):001:0> require 'test'
Initialization etc. gets done here
=> true
SWIG常数
可以在接口文件中以多种方式定义C/C++
常量。 要验证是否向Ruby模块公开了相同的常量,只需在加载共享库时在IRB提示符下键入<module-name>.constants
。 您可以通过以下任意一种方式定义常量:
- 在接口文件中使用
#define
- 使用
enum
- 使用
%constant
指令
请注意,Ruby常数必须以大写字母开头。 因此,如果您的接口文件包含#define pi 3.1415
类的声明,SWIG会自动将其更正为#define Pi 3.1415
并在此过程中生成警告消息:
bash$ swig –c++ –ruby test.i
test.i(3) : Warning 801: Wrong constant name (corrected to 'Pi')
下面的示例包含许多常量。 作为swig –ruby test.i
运行它:
%module test
#define S_Hello "Hello World"
%constant double PI = 3.1415
enum days {Sunday = 1, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday};
清单1显示了SWIG的输出。
清单1.将C枚举暴露给Ruby:出了什么问题?
test_wrap.c: In function `Init_test':
test_wrap.c:2147: error: `Sunday' undeclared (first use in this function)
test_wrap.c:2147: error: (Each undeclared identifier is reported only once
test_wrap.c:2147: error: for each function it appears in.)
test_wrap.c:2148: error: `Monday' undeclared (first use in this function)
test_wrap.c:2149: error: `Tuesday' undeclared (first use in this function)
test_wrap.c:2150: error: `Wednesday' undeclared (first use in this function)
test_wrap.c:2151: error: `Thursday' undeclared (first use in this function)
test_wrap.c:2152: error: `Friday' undeclared (first use in this function)
test_wrap.c:2153: error: `Saturday' undeclared (first use in this function)
糟糕:发生了什么事? 如果打开test_wrap.c( 清单2 ),则可以看到问题。
清单2.了解SWIG为枚举生成的代码
rb_define_const(mTest, "Sunday", SWIG_From_int((int)(Sunday)));
rb_define_const(mTest, "Monday", SWIG_From_int((int)(Monday)));
rb_define_const(mTest, "Tuesday", SWIG_From_int((int)(Tuesday)));
rb_define_const(mTest, "Wednesday", SWIG_From_int((int)(Wednesday)));
rb_define_const(mTest, "Thursday", SWIG_From_int((int)(Thursday)));
rb_define_const(mTest, "Friday", SWIG_From_int((int)(Friday)));
rb_define_const(mTest, "Saturday", SWIG_From_int((int)(Saturday)));
SWIG正在星期日,星期一和其他日期之外创建Ruby常数,除了在生成的文件中缺少day
的原始enum
声明。 解决此问题的最简单方法是将enum
代码放入%{ … %}
块中,以便生成的文件知道枚举常量,如清单3所示。
清单3.以正确的方式向Ruby提供C枚举
%module test
#define S_Hello "Hello World"
%constant double PI = 3.1415
enum days {Sunday = 1, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday};
%{
enum days {Sunday = 1, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday};
%}
请注意,仅enum
声明不会使枚举常量可用于脚本环境:您既需要%{ … %}
的C
代码,也需要接口文件中的enum
声明。
介绍%inline特殊指令
清单3很丑陋-不需要进行enum
代码重复。 要删除重复项,您需要使用%inline
SWIG指令。 %inline
指令将所有代码逐字跟在%{ … %}
块中插入接口文件中,以便同时满足SWIG预处理程序和C
编译器的要求。 清单4显示了修改后的代码,该enum
现在与%inline
。
清单4.使用%inline指令减少代码重复
%module test
#define S_Hello "Hello World"
%constant double PI = 3.1415
%inline %{
enum days {Sunday = 1, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday};
%}
%include是更干净的方法
在复杂的企业环境中,可能会有C/C++
标头定义要公开给脚本框架的全局变量和常量。 在接口文件中使用%include <header.h>
和%{ #include <header.h> %}
解决了标题中所有元素重复声明的问题。 清单5显示了代码。
清单5.使用%include指令
%module test
%include "header.h"
%{
#include "header.h"
%}
%include
指令也可用于C/C++
源文件。 与源文件一起使用时,SWIG会自动将所有函数声明为extern
。
足够的常量:让我们公开一些函数
开始学习SWIG的最简单方法是在接口文件中声明一些C
函数,在某些源文件中定义它,并在创建共享库时链接相应的对象文件。 第一个示例显示了一个计算数字阶乘的函数:
%module test
unsigned long factorial(unsigned long);
这是我在创建test.so时编译为factorial.o并链接的C
代码:
unsigned long factorial(unsigned long n) {
return n == 1 ? 1 : n * factorial(n - 1);
}
清单6显示了Ruby接口。
清单6.测试来自Ruby的代码
irb(main):001:0> require 'test'
=> true
irb(main):002:0> Test.factorial(11)
=> 39916800
irb(main):003:0> Test.factorial(34)
=> 0
阶乘34失败,因为unsigned long
的宽度不足以捕获结果。
Ruby到C / C ++的变量映射
让我们从简单的全局变量开始。 请注意, C/C++
全局变量并不是Ruby真正的全局变量:您只能将它们作为模块属性来访问。 在C
文件中添加以下全局变量,并使源链接的方式与对函数的链接方式几乎相同。 SWIG会自动为您生成这些变量的setter和getter方法。 这是C
代码:
int global_int1;
long global_long1;
float global_float1;
double global_double1;
清单7显示了相同的接口。
清单7.将C接口暴露给Ruby
%module test
%inline %{
extern int global_int1;
extern long global_long1;
extern float global_float1;
extern double global_double1;
%}
现在,加载相应的Ruby模块以验证setter和getter方法的添加:
irb(main):003:0> Test.methods
[…"global_float1", "global_float1=", "global_int1", "global_int1=", "global_long1",
"global_long1=", "global_double1", "global_double1=", …]
现在访问变量很简单:
irb(main):004:0> Test.global_long1 = 4327911
=> 4327911
irb(main):005:0> puts Test.global_long1
=> 4327911
Ruby尤其感兴趣的是Ruby将int
, long
, float
和double
为。 参见清单8 。
清单8. Ruby和C / C ++之间的类型映射
irb(main):009:0> Test::global_long1.class
=> Fixnum
irb(main):010:0> Test::global_int1.class
=> Fixnum
irb(main):011:0> Test::global_double1.class
=> Float
irb(main):012:0> Test::global_float1.class
=> Float
将结构和类从C ++映射到Ruby
向Ruby公开结构和类与C/C++
普通旧数据类型相同。 您只需在接口文件中声明结构和相关方法。 清单9声明了一个简单的Point
结构以及一个计算它们之间距离的函数。 在Ruby方面,您将创建一个新的Point
作为Test::Point.new
并将计算距离作为Test.distance_between
调用。 distance_between
函数是在与模块共享库链接的单独的C++
源文件中定义的。 这是SWIG接口代码:
清单9.向Ruby公开结构和相关接口
%module test
%inline %{
typedef struct Point {
int x;
int y;
};
extern float distance_between(Point& p1, Point& p2);
%}
清单10显示了Ruby的用法。
清单10.从Ruby验证C / C ++功能
irb(main):002:0> a = Test::Point.new
=> #<Test::Point:0x2d04260>
irb(main):003:0> a.x = 10
=> 10
irb(main):004:0> a.y = 20
=> 20
irb(main):005:0> b = Test::Point.new
=> #<Test::Point:0x2cce668>
irb(main):006:0> b.x = 20
=> 20
irb(main):007:0> b.y = 10
=> 10
irb(main):008:0> Test.distance_between(a, b)
=> 14.1421356201172
这个使用模型应该清楚地说明为什么SWIG是在为代码库设置单元或集成测试框架时非常有用的便捷工具。
%defaultctor和其他属性
如果检查出点的x和y坐标的默认值,则会看到它们的值为0。这不是巧合。 SWIG正在为您的结构生成默认构造函数。 您可以通过指定%nodefaultctor Point;
来关闭此行为%nodefaultctor Point;
在界面文件中。 清单11显示了如何。
清单11.没有用于C ++结构的默认构造函数
%module test
%nodefaultctor Point;
%inline %{
typedef struct Point {
int x;
int y;
};
%}
现在,您还需要为Point
结构提供一个显式的构造函数。 否则,您将看到以下代码:
irb(main):005:0> a = Test::Point.new
TypeError: allocator undefined for Test::Point
from (irb):5:in `new'
from (irb):5
通过指定%nodefaultctor;
可以使每个结构明确定义其构造函数%nodefaultctor;
在界面文件中。 SWIG还为析构函数中的类似功能定义了%nodefaultdtor
指令。
C ++继承和Ruby接口
为简单起见,假设你有两个C++
类- Base
和Derived
-in接口文件。 SWIG完全意识到Derived
是从Base
Derived
。 从Ruby的角度来看,您可以简单地使用Derived.new
并安全地期望所创建的对象知道它是从Base
派生的。 清单12显示了Ruby测试代码; 在C++
或SWIG接口方面不需要做任何特定的事情。
清单12. SWIG接口处理C ++继承
irb(main):003:0> a = Test::Derived.new
=> #<Test::Derived:0x2d06270>
irb(main):004:0> a.instance_of? Test::Derived
=> true
irb(main):005:0> a.instance_of? Test::Base
=> false
irb(main):006:0> Test::Derived < Test::Base
=> true
irb(main):007:0> Test::Derived > Test::Base
=> false
irb(main):008:0> a.is_a? Test::Derived
=> true
irb(main):009:0> a.is_a? Test::Base
=> true
使用C++
多重继承处理起来并不那么顺利。 如果Derived
是从Base1
和Base2
继承的,则默认的SWIG行为是简单地忽略Base2
。 这是您将从SWIG获得的消息:
Warning 802: Warning for Derived d: base Base2 ignored.
Multiple inheritance is not supported in Ruby.
坦白说,SWIG不会出错,因为Ruby不支持多重继承。 为了使SWIG正常工作,您需要在命令行中传递–minherit
选项:
bash$ swig -ruby -minherit -c++ test.i
了解SWIG如何处理多重继承非常重要。 C++
的派生类对应于Ruby中既不是从Base1
也不是从Base2
派生的类。 而是将Base1
和Base2
代码重构为模块并包含在Derived
。 在Ruby术语中,这就是所谓的mixin。 清单13显示了正在发生的事情的伪代码。
清单13.使用Ruby模拟多重继承
class Base1
module Impl
# Define Base1 methods here
end
include Impl
end
class Base2
module Impl
# Define Base2 methods here
end
include Impl
end
class Derived
module Impl
include Base1::Impl
include Base2::Impl
# Define Derived methods here
end
include Impl
end
让我们从Ruby接口验证声明。 如清单14所示, included_modules
方法可以为您解决问题。
清单14.作为Ruby类的一部分的多个模块
irb> Test::Derived.included_modules
=> [Test::Derived::Impl, Test::Base::Impl, Test::Base2::Impl, Kernel]
irb> Test::Derived < Test::Base
=> nil
irb> Test::Derived < Test::Base2
=> nil
请注意,类层次结构测试失败了(应该如此),但是对于应用程序开发人员而言, Base
和Base2
的功能仍然可以通过Derived
类使用。
指针和Ruby接口
Ruby没有等效的指针,那么接受或返回指针的C/C++
方法会怎样? 这给我们带来的像痛饮系统,其主要工作是转换的最重要的挑战之一(或元帅,因为他们说的)数据类型的源语言和目标语言之间。 考虑以下C
函数:
void addition(const int* n1, const int* n2, int* result) {
*result = *n1 + *n2;
}
为了解决此问题,SWIG引入了类型映射的概念。 您可以灵活地映射要映射到int*
, float*
和其余类型的Ruby类型。 值得庆幸的是,SWIG已经为您完成了大部分样板工作。 因此,这是您可能需要添加的最简单的接口:
%module Test
%include typemaps.i
void addition (int* INPUT, int* INPUT, int* OUTPUT);
%{ extern void addition(int*, int*, int*); %}
现在,尝试使用Ruby中的代码作为Test::addition(1, 2)
。 您应该能够看到结果。 要了解有关此处发生的事件的更多信息,请查看lib / ruby文件夹。 SWIG使用int* INPUT
语法将基础指针转换为对象。 用于将类型从Ruby映射到C/C++
的SWIG语法为:
%typemap(in) int* {
… type conversion code from Ruby to C/C++
}
同样,从C/C++
到Ruby的类型转换代码为:
%typemap(out) int* {
… type convesion code from C/C++ to Ruby
}
类型映射不仅仅适用于指针:您可以将它们用于Ruby和C/C++
之间的几乎任何数据类型转换。
结论
在本文中,您学习了如何公开C/C++
常量。 变量,包括结构和类; 功能; 以及Ruby接口的枚举。 在此过程中,您选择了诸如%module
, %init
, %constant
, %inline
, %include
和%nodefaultctor
类的SWIG指令。 SWIG提供更多功能; 请务必查看SWIG文档随附的出色的PDF文档,以获取更多详细信息。
翻译自: https://www.ibm.com/developerworks/aix/library/au-swig/index.html