内蒙古java团队

j2se,j2ee开发组
posts - 139, comments - 212, trackbacks - 0, articles - 65
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

MapGuide Web API

Posted on 2011-04-15 00:18 帅子 阅读(716) 评论(0)  编辑  收藏 所属分类: MapGuide

许多人都知道MapGuide提供了.NET、PHP和Java三种类型的Web API,但是不知道MapGuide是如何创建这三种类型的API的。试想一下,如果分别去创建这三种API,这将是一个很难维护的工作。每次增加或修改一些功能,就需要对三种类型的API都进行修改。所以,MapGuide使用了SWIG来自动生成这三种类型的API。我想这个时候许多人会问,什么是SWIG呢?我怎么从来没有听说过这个东东呢!其实,我也是在做MapGuide开发的时候才开始了解SWIG的。所以,首先让我们来认识一下SWIG,然后再来看MapGuide是如何使用SWIG来生成API的。

1. SWIG简介

    SWIG是Simple Wrapper and Interface Generator的缩写,是一个帮助使用C或者C++编写的软件创建其他编语言的API的工具。例如,我想要为一个C++编写的程序创建.NET API,一般情况下我必须使用托管C++(Managed C++)去编写大量的代码才能生成它的.NET API。有了SWIG,这个机械的工作将变得非常简单。你只须要使用一个接口文件告诉SWIG要为那些类创建.NET API,SWIG就会自动帮你生成它的.NET API。是不是非常的酷啊?

    当然,SWIG不仅仅支持创建.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是如何帮我们创建API的。假设我打算为如下的C++类创建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要为那些类的那些方法创建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生成的模块的名称,%{%}标记中的内容会被一字不差地插入SWIG自动生成的文件xxx_wrapper.c中,其中xxx代表用%module指定的模块名称。这个文件会在下面介绍,不必着急去理解它究竟有什么作用。

    如果打算为类中所有方法创建API,那么有一个非常简单的办法,在接口文件的类声明部分使用%include标记。SWIG将对%include所指定的文件进行语法分析,类中所有公有方法(Public Method)都将在API中暴露。

    /* SwigTest.i */
    %module SwigTest
    %{
    #include "SwigTest.h"
    %}
    #include “SwigTest.h” 

1.2 编译模块

    有了接口文件之后,剩下的事就是执行几条命令。下面我们以Windows平台上生成.NET API为例介绍这些命令。

    (a) 调用SWIG自动生成代码

    swig -csharp  SwigTest.i

    执行上面的命令会产生一个C语言文件SwigTest_wrapper.c和多个C#文件。在文件SwigTest_wrapper.c中,SWIG为接口文件中接口声明部分指定的每个方法产生一个全局方法,以便C#使用Pinvoke调用这些函数。而那些C#文件就是用来生成.NET API的。

    (b) 为C++代码生成DLL(动态链接库)

    cl SwigTest_wrapper.c *.cpp

    link *.obj /out:SwigTest.dll

    执行上面的命令,会为我们编写C++代码生成DLL。在编译C++文件时,一定要包括SWIG为我们生成的C++文件SwigTest_wrapper.cpp。

    注意:为了让大家便于理解上述命令,这些命令并没有列出完整的编译和链接选项。

    (C) 生成.NET模块

    csc /out:SwigTestNotNetAPI.dll /target:library *.cs

    执行上面的命令就生成了.NET API模块SwigTestNotNetAPI.dll。如果用户想使用这些API,只需要添加对SwigTestNotNetAPI.dll的引用(Reference)就可以了。

    生成其它语言类型API的命令基本类似,下面我们再以Java在Unix平台下的命令为例结束对SWIG的介绍。事实上,SWIG也是一个开源项目。如果想了解更多关于SWIG的信息,大家可以登陆SWIG的官方网站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中的应用

    我们在前面已经提到过,MapGuide使用了SWIG来自动生成.NET、Java和PHP这三种类型的API。但是,SWIG也有不少限制和缺陷,所以MapGuide对SWIG源代码进行了大量的修改,以满足自己的要求。下面,我们看看这些改进。

2.1 IMake工具

    SWIG要求开发人员编写一个接口文件,那么能否让接口文件自动生成呢?借用一句中国移动的广告词,我能!虽然SWIG没有提供这方面的工具,但是我们可以自己开发吗!IMake(Interface Maker)就是为了满足这样的要求而开发一个工具,给定一个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的用法。为了便于理解,在此我删掉了文件中的部分内容。

    <?xml version="1.0" encoding="UTF-8"?>
    <Parameters>
      <!-- 对应于%Module标记. -->
      <Module name="MapGuideApi" />

      <!-- 生成的接口文件的名称. -->
      <Target path="./MapGuideApi.i" />

      <!-- 对应于%{%}标记 -->
      <CppInline>
        #include &lt;string&gt;
        #include &lt;map&gt;
        #include "MapGuideCommon.h"
        #include "WebApp.h"
        ......
      </CppInline>

      <!-- 用于替换接口中使用的部分类型 -->
      <TypeReplacements>
        <TypeReplacement oldtype="CREFSTRING" newtype="STRINGPARAM" />
        <TypeReplacement oldtype="INT64" newtype="long long" />
      </TypeReplacements>

      <!-- 此部分的内容添加在%{%}之后,接口声明部分之前 -->
      <SwigInline>
        %include "language.i"   //typemaps specific for each language
        ......
      </SwigInline>
      
      <!-- 为指定的C++文件生成接口声明 -->
      <Headers>
        <Header path="../../../Common/Foundation/Data/Property.h" />
        ......
      </Headers>
    </Parameters>

    执行命令“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为例,介绍如何自动生成各种语言的常量定义文件。同样,为了便于理解,在此我删掉了文件中的部分内容。与MapGuideApiGen.xml不同,Constants.xml包含一个新的元素Classes用来指出需要在目标语言中产生对应的常量类的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>

    <!-- 用于指出需要在目标语言中产生对应的常量类的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>

    执行命令“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命令的参数"C#"替换为"PHP"或"Jave"就可以了。

2.2 MapGuide对SWIG的修改
    在MapGuide开始使用SWIG的时候,可用的SWIG的最高版本是1.3.21,从那以后MapGuide在没有升级过SWIG。所以,到现在为止,MapGuide的SWIG版本仍然是1.3.21。这个版本的SWIG有不少限制和缺陷,

  • 无法创建基于自定义根异常类MgException的异常处理机制。
  • 无法创建属性(Property)。
  • 对某些方法无法产生正确的API。例如,如果方法GetA(...)返回的是类A的子类B的实例,SWIG创建的API返回的仍然是A类的实例。此时如果你把返回值转换为类B,那么转换会失败。
        A* GetA();
  • ......

    事实上最新的SWIG版本也没有全部解决这些问题,所以MapGuide对SWIG源代码进行了大量的修改,以满足自己的要求。看看MapGuide在使用SWIG命令是传入的参数,我们可以发现有许多参数不是SWIG标准的参数,例如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 &quot;((MgDisposable*)arg1)-&gt;Release()&quot; -rethrow &quot;e-&gt;Raise();&quot; -nodefault -noconstants -module MapGuideApi -o MgApi_wrap.cpp -lib ..\..\..\Oem\SWIGEx\Lib MapGuideApi.i


    在此,我们不打算一一介绍这些参数,因为在多数情况下你没有必要对了解参数的含义。我们只介绍MapGuide是如何来解决上述SWIG的第二和第三个问题的,因为在扩展MapGuide Web API的时候你可能会用得着。


2.2.1 创建属性
    如果你看过MapGuide源代码的话,你会发现有许多方法声明之后有“__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++头文件时发现这注释后,会在目录“.\custom”下为每个类产生一个帮助创建属性的代码文件。例如,如果要类MgProperty生成.NET API,IMake会在“.\custom”生成一个文件名为MgProperty的C#代码文件,它的内容如下:
    public int PropertyType {
        get {return GetPropertyType(); }
    }
    public int Name {
        get { return GetPropertyType(); }
        set { setName(value);}
    }
    如果在SWIG的命令行中使用了参数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

    如果你发现现有的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/)。
 
    如果要增加一些新的方法到现有的类中,基本步骤如下:
    (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/)。


只有注册用户登录后才能发表评论。


网站导航: