許多人都知道MapGuide提供了.NET、PHP和Java三種類型的Web API,但是不知道MapGuide是如何創(chuàng)建這三種類型的API的。試想一下,如果分別去創(chuàng)建這三種API,這將是一個很難維護的工作。每次增加或修改一些功能,就需要對三種類型的API都進行修改。所以,MapGuide使用了SWIG來自動生成這三種類型的API。我想這個時候許多人會問,什么是SWIG呢?我怎么從來沒有聽說過這個東東呢!其實,我也是在做MapGuide開發(fā)的時候才開始了解SWIG的。所以,首先讓我們來認識一下SWIG,然后再來看MapGuide是如何使用SWIG來生成API的。
1. SWIG簡介
??? SWIG是Simple Wrapper and Interface Generator的縮寫,是一個幫助使用C或者C++編寫的軟件創(chuàng)建其他編語言的API的工具。例如,我想要為一個C++編寫的程序創(chuàng)建.NET API,一般情況下我必須使用托管C++(Managed C++)去編寫大量的代碼才能生成它的.NET API。有了SWIG,這個機械的工作將變得非常簡單。你只須要使用一個接口文件告訴SWIG要為那些類創(chuàng)建.NET API,SWIG就會自動幫你生成它的.NET API。是不是非常的酷啊?
??? 當然,SWIG不僅僅支持創(chuàng)建.NET API。最新版本的SWIG支持常用腳本語言Perl、PHP、Python、Tcl、Ruby和非腳本語言C#, Common Lisp (CLISP, Allegro CL, CFFI, UFFI), Java, Modula-3, OCAML以及R,甚至是編譯器或者匯編的計劃應用(Guile, MzScheme, Chicken)。
??? 下面我們通過一個例子來看看SWIG是如何幫我們創(chuàng)建API的。假設我打算為如下的C++類創(chuàng)建C#和Java的API。
??? /* SwigTest.h */
??? class CSwigTest {
??? public:
??????? CSwigTest();
??? ??? virtual ~CSwigTest();
??????? int Add(int a, int b) { return a + b; }
??? ??? int Substract(int a, int b) { return a - b; }
??? ??? int Multiple(int a, int b) { return a * b; }
??????? float Divide(int a, int b) { return (float)a / (float)b; }
??? };
1.1 接口文件
??? 首先,你需要寫一個接口文件(Interface File),告訴SWIG要為那些類的那些方法創(chuàng)建API。如下的接口文件只為類CSwigTest的方法Add(...)和Subtract(...)生成API,因為在接口文件的接口聲明部分只聲明了兩個方法。
??? /* SwigTest.i */
??? %module SwigTest
??? %{
??? #include "SwigTest.h"
??? %}
? ? /*?--- 接口聲明部分 ---*/
??? class CSwigTest {
??? public:
??????? int Add(int a, int b);
??????? int Substract(int a, int b);
??? };
??? 注解:%module標記用于定義SWIG生成的模塊的名稱,%{%}標記中的內(nèi)容會被一字不差地插入SWIG自動生成的文件xxx_wrapper.c中,其中xxx代表用%module指定的模塊名稱。這個文件會在下面介紹,不必著急去理解它究竟有什么作用。
??? 如果打算為類中所有方法創(chuàng)建API,那么有一個非常簡單的辦法,在接口文件的類聲明部分使用%include標記。SWIG將對%include所指定的文件進行語法分析,類中所有公有方法(Public Method)都將在API中暴露。
??? /* SwigTest.i */
??? %module SwigTest
??? %{
??? #include "SwigTest.h"
??? %}
??? #include “SwigTest.h”?
1.2 編譯模塊
??? 有了接口文件之后,剩下的事就是執(zhí)行幾條命令。下面我們以Windows平臺上生成.NET API為例介紹這些命令。
??? (a) 調(diào)用SWIG自動生成代碼
??? swig -csharp? SwigTest.i
??? 執(zhí)行上面的命令會產(chǎn)生一個C語言文件SwigTest_wrapper.c和多個C#文件。在文件SwigTest_wrapper.c中,SWIG為接口文件中接口聲明部分指定的每個方法產(chǎn)生一個全局方法,以便C#使用Pinvoke調(diào)用這些函數(shù)。而那些C#文件就是用來生成.NET API的。
????(b) 為C++代碼生成DLL(動態(tài)鏈接庫)
??? cl SwigTest_wrapper.c *.cpp
??? link *.obj /out:SwigTest.dll
??? 執(zhí)行上面的命令,會為我們編寫C++代碼生成DLL。在編譯C++文件時,一定要包括SWIG為我們生成的C++文件SwigTest_wrapper.cpp。
??? 注意:為了讓大家便于理解上述命令,這些命令并沒有列出完整的編譯和鏈接選項。
????(C) 生成.NET模塊
??? csc /out:SwigTestNotNetAPI.dll /target:library *.cs
??? 執(zhí)行上面的命令就生成了.NET API模塊SwigTestNotNetAPI.dll。如果用戶想使用這些API,只需要添加對SwigTestNotNetAPI.dll的引用(Reference)就可以了。
??? 生成其它語言類型API的命令基本類似,下面我們再以Java在Unix平臺下的命令為例結束對SWIG的介紹。事實上,SWIG也是一個開源項目。如果想了解更多關于SWIG的信息,大家可以登陸SWIG的官方網(wǎng)站www.swig.org,那里有SWIG最詳細的資料。
????$ swig -java SwigTest_wrapper.i
?? ?$ gcc -c *.cpp SwigTest_wrapper.c -I/c/jdk1.3.1/include -I/c/jdk1.3.1/include/win32
?? ?$ gcc -shared *.o -mno-cygwin -Wl,--add-stdcall-alias? -o SwigTest.dll
2. SWIG在MapGuide中的應用
??? 我們在前面已經(jīng)提到過,MapGuide使用了SWIG來自動生成.NET、Java和PHP這三種類型的API。但是,SWIG也有不少限制和缺陷,所以MapGuide對SWIG源代碼進行了大量的修改,以滿足自己的要求。下面,我們看看這些改進。
2.1 IMake工具
??? SWIG要求開發(fā)人員編寫一個接口文件,那么能否讓接口文件自動生成呢?借用一句中國移動的廣告詞,我能!雖然SWIG沒有提供這方面的工具,但是我們可以自己開發(fā)嗎!IMake(Interface Maker)就是為了滿足這樣的要求而開發(fā)一個工具,給定一個XML文件,它能幫你自動生成SWIG接口文件。登錄MapGuide開源版的代碼瀏覽頁面(http://trac.osgeo.org/mapguide/browser),在root/trunk/MgDev/BuildTools/WebTools/IMake文件夾下可以找到IMake的源代碼。
???? 下面我們以MapGuide中使用的XML文件/trunk/MgDev/Web/src/MapGuideApi/MapGuideApiGen.xml為例,介紹一下IMake的用法。為了便于理解,在此我刪掉了文件中的部分內(nèi)容。
??? <?xml version="1.0" encoding="UTF-8"?>
??? <Parameters>
????? <!-- 對應于%Module標記. -->
????? <Module name="MapGuideApi" />
????? <!-- 生成的接口文件的名稱. -->
????? <Target path="./MapGuideApi.i" />
????? <!-- 對應于%{%}標記 -->
????? <CppInline>
??????? #include <string>
??????? #include <map>
??????? #include "MapGuideCommon.h"
??????? #include "WebApp.h"
??????? ......
????? </CppInline>
????? <!-- 用于替換接口中使用的部分類型 -->
????? <TypeReplacements>
??????? <TypeReplacement oldtype="CREFSTRING" newtype="STRINGPARAM" />
??????? <TypeReplacement oldtype="INT64" newtype="long long" />
????? </TypeReplacements>
????? <!-- 此部分的內(nèi)容添加在%{%}之后,接口聲明部分之前 -->
????? <SwigInline>
??????? %include "language.i"?? //typemaps specific for each language
??????? ......
????? </SwigInline>
??????
????? <!-- 為指定的C++文件生成接口聲明 -->
????? <Headers>
??????? <Header path="../../../Common/Foundation/Data/Property.h" />
??????? ......
????? </Headers>
??? </Parameters>
??? 執(zhí)行命令“IMake MapGuideApiGen.xml”,IMake就幫我們自動生成了如下SWIG接口文件MapGuideApi.i。
??? /* MapGuideApi.i */
??? %module MapGuideApi?
??? %{
??????? #include <string>;
??????? #include <map>;
??????? #include "MapGuideCommon.h"
??????? #include "WebApp.h"
??????? ......
??? %}
?
??? %include "language.i"?? //typemaps specific for each language
??? ......
??? class MgProperty: public MgNamedSerializable
??? {
??? public:
??????? virtual INT16 GetPropertyType();
??????? STRING GetName();
??????? void SetName(CREFSTRING name);
??? };
??? ......
??? 如果打開文件Proper.h,我們可以看到MgProperty有更多的方法,例如CanSetName(...)。為什么只有三個方法添加到了SWIG接口文件中?IMake在生成接口文件時,它會查找C++頭文件中的宏PUBLISHED_API。只有被PUBLISHED_API修飾的方法,才會添加到接口文件中。
??? 注:宏PUBLISHED_API和INTERNAL_API的定義如下。
??? #define PUBLISHED_API public
??? #define INTERNAL_API public
??? class MG_FOUNDATION_API MgProperty : public MgNamedSerializable?
??? {?
??? PUBLISHED_API:
??????? virtual INT16 GetPropertyType() = 0;? /// __get???
??????? STRING GetName();? /// __get, __set?
??????? void SetName(CREFSTRING name);?
??
??? INTERNAL_API:?
??????? virtual bool CanSetName();?
??
??? protected:??
??????? INT32 GetClassId();?
??????? MgProperty();?
??????? virtual ~MgProperty();?
??????? virtual void Dispose();???
??????? virtual void ToXml(string &str, bool includeType = true, string rootElmName = "Property") = 0;?
??
??? private:?
??????? friend class MgPropertyCollection;?
??????? STRING m_propertyName;?
?
??? CLASS_ID:?
??????? static const INT32 m_cls_id = Foundation_Property_Property;?
??? };?????
??? 給定一個C++常量定義文件,IMake還可以自動生成對應的其他語言的常量定義文件。MapGuide .NET Web API中的所有常量都是使用IMake來生成的,例如MgMineType、MgPropertyType等。下面我們以MapGuide中使用的XML文件/trunk/MgDev/Web/src/MapGuideApi/Constants.xml為例,介紹如何自動生成各種語言的常量定義文件。同樣,為了便于理解,在此我刪掉了文件中的部分內(nèi)容。與MapGuideApiGen.xml不同,Constants.xml包含一個新的元素Classes用來指出需要在目標語言中產(chǎn)生對應的常量類的C++類。
??? <?xml version="1.0" encoding="UTF-8"?>
??? <Parameters>
??? <!-- 用于替換類型 -->
??? <PHPTypeReplacements>?
??????? <TypeReplacement oldtype="STRING" newtype="" />
??????? <TypeReplacement oldtype="INT16" newtype="" />
??????? ......
??? </PHPTypeReplacements>
??? <CSharpTypeReplacements>
??????? <TypeReplacement oldtype="STRING" newtype="string" />
??????? <TypeReplacement oldtype="INT16" newtype="short" />
??????? ......
??? </CSharpTypeReplacements>
??? <JavaTypeReplacements>
??????? <TypeReplacement oldtype="STRING" newtype="String" />
??????? <TypeReplacement oldtype="INT16" newtype="short" />
??????? ......
??? </JavaTypeReplacements>
??? <Namespace>OSGeo.MapGuide</Namespace>
??? <Package>org.osgeo.mapguide</Package>
??? <!--?用于指出需要在目標語言中產(chǎn)生對應的常量類的C++類 -->
??? <Classes>
??????? <Class name="MgMineType" />
??????? <Class name="MgPropertyType" />
??????? ......
??? </Classes>
??? <Headers>
??????? <Header path="../../../Common/Foundation/Data/MimeType.h" />
??????? <Header path="../../../Common/Foundation/Data/PropertyType.h" />
??????? ......
??? </Headers>
??? </Parameters>
??? 執(zhí)行命令“IMake.exe Constants.xml C# Constants.cs”,IMake就幫我們自動生成了一個C#常量文件Constants.cs。對于文件/trunk/MgDev/Common/Foundation/Data/PropertyType.h中定義了如下常量,
??? class MgPropertyType?
??? {?
??? PUBLISHED_API:??
?????? static const int Null???? =? 0;
?????? static const int Boolean? =? 1;?
?????? static const int Byte???? =? 2;?
?????? static const int DateTime =? 3;
?????? static const int Single?? =? 4;?
?????? ......
??? };
??? 在生成的Constants.cs文件中,有如下的類定義。
??? class MgPropertyType?
??? {?
?????? static const int Null???? =? 0;
?????? static const int Boolean? =? 1;?
?????? static const int Byte???? =? 2;?
?????? static const int DateTime =? 3;
?????? static const int Single?? =? 4;?
?????? ......
??? };
??? 這個文件可以被C#的編譯器直接編譯,所以MapGuide沒有使用SWIG生成常量的API,而是直接使用IMake。?如果想生成PHP或Java的常量定義文件,只需要將IMake命令的參數(shù)"C#"替換為"PHP"或"Jave"就可以了。
2.2 MapGuide對SWIG的修改
??? 在MapGuide開始使用SWIG的時候,可用的SWIG的最高版本是1.3.21,從那以后MapGuide在沒有升級過SWIG。所以,到現(xiàn)在為止,MapGuide的SWIG版本仍然是1.3.21。這個版本的SWIG有不少限制和缺陷,
- 無法創(chuàng)建基于自定義根異常類MgException的異常處理機制。
- 無法創(chuàng)建屬性(Property)。
- 對某些方法無法產(chǎn)生正確的API。例如,如果方法GetA(...)返回的是類A的子類B的實例,SWIG創(chuàng)建的API返回的仍然是A類的實例。此時如果你把返回值轉換為類B,那么轉換會失敗。
??? A* GetA();
- ......
??? 事實上最新的SWIG版本也沒有全部解決這些問題,所以MapGuide對SWIG源代碼進行了大量的修改,以滿足自己的要求。看看MapGuide在使用SWIG命令是傳入的參數(shù),我們可以發(fā)現(xiàn)有許多參數(shù)不是SWIG標準的參數(shù),例如proxydir、clsidcode、clsiddata、catchallcode等。
swig -c++ -csharp -dllname MapGuideUnmanagedApid -namespace OSGeo.MapGuide -proxydir .\custom -baseexception MgException -clsidcode getclassid.code -clsiddata m_cls_id -catchallcode catchall.code -dispose "((MgDisposable*)arg1)->Release()" -rethrow "e->Raise();" -nodefault -noconstants -module MapGuideApi -o MgApi_wrap.cpp -lib ..\..\..\Oem\SWIGEx\Lib MapGuideApi.i
??? 在此,我們不打算一一介紹這些參數(shù),因為在多數(shù)情況下你沒有必要對了解參數(shù)的含義。我們只介紹MapGuide是如何來解決上述SWIG的第二和第三個問題的,因為在擴展MapGuide Web API的時候你可能會用得著。
2.2.1 創(chuàng)建屬性
??? 如果你看過MapGuide源代碼的話,你會發(fā)現(xiàn)有許多方法聲明之后有“__get”、“__set”或“__get, __set”這樣的注釋,如類MgProperty中的方法。
??? class MgProperty : public MgNamedSerializable?
??? {?
??? PUBLISHED_API:?
??????? virtual INT16 GetPropertyType() = 0;? /// __get???
??????? STRING GetName();? /// __get, __set?
??????? void SetName(CREFSTRING name);?
??????? ......
};
??? 這些注釋是有特殊含義的,它們就是用來解決上述SWIG的第二個問題的。當IMake工具掃描C++頭文件時發(fā)現(xiàn)這注釋后,會在目錄“.\custom”下為每個類產(chǎn)生一個幫助創(chuàng)建屬性的代碼文件。例如,如果要類MgProperty生成.NET API,IMake會在“.\custom”生成一個文件名為MgProperty的C#代碼文件,它的內(nèi)容如下:
??? public int PropertyType {
??????? get {return GetPropertyType(); }
??? }
??? public int Name {
??????? get { return GetPropertyType(); }
??????? set { setName(value);}
??? }
??? 如果在SWIG的命令行中使用了參數(shù)proxydir,那么SWIG在為每個類生成代碼的時候,會在proxydir所指定的目錄下查找和類名相同的文件,并且將這個文件中的代碼插入類的目標代碼中。通過這種辦法,就解決了上述SWIG的第二個問題。
2.2.2 ClassId
??? MapGuide Web API中的所有類都是從MgObject繼承而來的,在類MgObject中有一個方法GetClassId()用來返回每個類唯一的ID值。MapGuide就是用這個方法來解決上述SWIG的第三個問題的,所以如果要在MapGuide Web API中增加一個新類,一定要覆蓋(override)這個方法,并且提供一個唯一的ID值。
??? class MgObject
??? {
??? EXTERNAL_API:
??????? virtual INT32 GetClassId();
??????? virtual STRING GetClassName();
??? INTERNAL_API:
??????? virtual ~MgObject();
??????? bool IsOfClass(INT32 classId);
??? };
3. 擴展MapGudie Web API
????如果你發(fā)現(xiàn)現(xiàn)有的MapGuide Web API無法滿足你的要求,沒有關系,你可以去嘗試擴展它,因為MapGuide是開源的。
??? 如果要新添類,基本步驟如下:
??? (a) 修改C++代碼,添加新的類。對于需要暴露于API的方法,使用宏PUBLISHED_API修飾。
??? (b) 修改XML文件/trunk/MgDev/Web/src/MapGuideApi/MapGuideApiGen.xml的Headers部分,為每個新添加類所在的C++頭文件增加一個Header元素。下面的示例中,"path"代表C++頭文件的路徑,"filename.h"代表文件的名稱。
??? <Headers>
??????? <Header path="path/filename.h" />
??????? ......
??? </Headers>
??? (c) 重新編譯MapGuide的Web模塊(/trunk/MgDev/Web/src/)。
?
??? 如果要增加一些新的方法到現(xiàn)有的類中,基本步驟如下:
??? (a) 修改C++代碼,添加新的方法,并且使用宏PUBLISHED_API修飾這些方法。
??? (b) 重新編譯MapGuide的Web模塊(/trunk/MgDev/Web/src/)。
??? 如果要新增常量類,基本步驟如下:
??? (a) 修改C++代碼,添加新的常量類。
??? (b) 修改XML文件/trunk/MgDev/Web/src/MapGuideApi/Constants.xml,在Classes部分為每個新添加常量類增加一個Class元素,在Headers部分為每個新添加常量類所在的C++頭文件增加一個Header元素。下面的示例中,"ClassName"代表新添加的C++常量類的名稱。
??? <Classes>
??????? <Class name="ClassName" />
??????? ......
??? </Classes>
??? <Headers>
??????? <Header path="path/filename.h" />
??????? ......
??? </Headers>????
??? (c) 重新編譯MapGuide的Web模塊(/trunk/MgDev/Web/src/)。