swig

    技术2024-06-14  89

    SWIG安装

    本文使用痛饮版本2.0.4(请参阅相关信息的链接,下载站点)。 要构建和安装SWIG,请遵循典型的开源安装过程,在命令提示符处输入以下命令:

    tar xvzf swig-2.0.4.tar.gz ./configure –prefix=/your/swig/install/path make make install

    注意,给前缀的路径必须是绝对路径。

    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调试起来容易得多。

    Ruby环境变量

    SWIG生成需要ruby.h才能正确编译的包装C/C++代码。 检查Ruby安装中的ruby.h:建议的做法是让环境变量RUBY_INCLUDE指向包含ruby.h的文件夹,而RUBY_LIB指向包含Ruby库的路径。

    带有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接口还允许您定义以%{开头并以%}结尾的块。 %{和%}之间的所有内容都被逐字复制到生成的包装文件中。

    有关模块名称的更多信息

    通过指定与%module "rubytest::test34::example相同的名称,可以定义像rubytest::test34::example这样的深层嵌套模块。另一种选择是在接口代码中包含%module example并在其rubytest::test34加上rubytest::test34在命令行中rubytest::test34如下:

    bash$ swig –c++ –ruby –prefix “rubytest::test34” test.i

    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

    Processed: 0.016, SQL: 9