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 轉換 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;
來關閉此行為。清單 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