SWIG 快速入門


SWIG 安裝

本文使用了 SWIG 版本 2.0.4(參見 參考資料 獲取下載站點的鏈接)。要構建和安裝 SWIG,可按照典型的開源安裝流程,在命令提示符下輸入以下命令:

請注意,為前綴提供的路徑必須是絕對路徑。

C 和 C++ 被公認為(理當如此)創建高性能代碼的首選平台。對開發人員的一個常見要求是向腳本語言接口公開 C/C++ 代碼,這正是 Simplified Wrapper and Interface Generator (SWIG) 的用武之地。SWIG 允許您向廣泛的腳本語言公開 C/C++ 代碼,包括 Ruby、Perl、Tcl 和 Python。本文使用 Ruby 作為公開 C/C++ 功能的首選腳本接口。要理解本文,您必須具備 C/C++ 與 Ruby 方面的相應知識。

SWIG 是一款不錯的工具,可適合多種場景,其中包括:

  • 向 C/C++ 代碼提供一個腳本接口,使用戶更容易使用
  • 向您的 Ruby 代碼添加擴展或將現有的模塊替換為高性能的替代模塊
  • 提供使用腳本環境對代碼執行單元和集成測試的能力
  • 使用 TK 開發一個圖形用戶接口並將它與 C/C++ 后端集成

此外,與 GNU Debugger 每次都需觸發相比,SWIG 要容易調試得多。

Ruby 環境變量

SWIG 生成包裝器 C/C++ 代碼時需要 ruby.h 來保證進行正確的編譯。在您的 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

就這么簡單。您已准備就緒,那就觸發交互式 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 預處理器處理它的輸入文件。此外,接口文件中的 SWIG 操作通過一個百分比符號 (%) 后跟的特殊的指令(%module%constant 等)來控制。SWIG 接口還允許您定義以 %{ 開頭和以 %} 結束的信息塊。%{ 和 %} 之間的所有內容會原封不動地復制到生成的包裝器文件中。

模塊名稱的更多信息

可通過指定 %module "rubytest::test34::example,定義一個深度嵌套模塊 rubytest::test34::example。另一個選項是將 %module example 放在接口代碼中,在命令行添加 rubytest::test34 作為它的前綴,如下所示:

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. 向 Ruby 公開 C 枚舉:哪里出錯了?
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 從 Sunday、Monday 等變量中創建 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);

以下是我編譯為 factorial.o 並在創建 test.so 時鏈接的 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

Factorial 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. 向 Ruby 公開 C 接口
%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 轉換 intlongfloat 和 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; 來關閉此行為。清單 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;,讓每個結構顯式定義自己的構造函數。SWIG 也為析構函數中的類似功能定義了 %nodefaultdtor 指令。


回頁首

C++ 繼承和 Ruby 接口

為簡單起見,假設接口函數中有兩個 C++ 類 —Base 和 Derived。SWIG 充分意識到 Derived派生自 Base。從 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 接口的聲明。included_modules 模塊為您完成了此任務,如 清單 14 中所示。

清單 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++ 方法怎么辦?這為我們帶來了 SWIG 這樣的系統的一個最重要的挑戰,這一系統的主要任務是在源和目標語言之間轉換(或俗稱編組)數據類型。仔細考慮下面的 C 函數:

void addition(const int* n1, const int* n2, int* result) { 
  *result = *n1 + *n2;
}

為解決這個問題,SWIG 引入了類型映射 的概念。您能夠靈活地將您想要的 Ruby 類型映射到 int*float* 等類型。幸運的是,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++ 之間的任何數據類型轉換。

 

 

轉自:http://www.360doc.com/content/14/1020/18/6828497_418466026.shtml


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM