swig_急于开发人员的SWIG

CC++作为创建高性能代码的首选平台而被广泛认可(正确的是)。 对开发人员的一个常见要求是他们从脚本语言界面公开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将intlongfloatdouble为。 参见清单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++类- BaseDerived -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是从Base1Base2继承的,则默认的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派生的类。 而是将Base1Base2代码重构为模块并包含在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

请注意,类层次结构测试失败了(应该如此),但是对于应用程序开发人员而言, BaseBase2的功能仍然可以通过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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值