Coding Guidelines: Web Server Extension API

This document describes the coding guidelines for the MapGuide Open Source Web Extensions API.  The API is written in C++ and made available to applications written in PHP, .Net and Java using a combination of automated tools – IMake and SWIG.  Since the API is consistent in all 3 languages, specific rules must be applied to the C++ class and method signatures to ensure cross-language compatibility.  This document details some of the rationale behind these rules and then elaborates on the language constructs required to implement the rules.

Rationale

MapGuide Open Source implements its common API in C++ and uses an interface generator called SWIG (http://www.swig.org) to export the API to PHP, .Net and Java.  SWIG uses a combination of C glue logic and managed .Net and Java wrapper code to expose the API.  A tool called IMake is used to interrogate C++ header files and generate a SWIG compatible interface definition.  By decorating the C++ headers and following specific guidelines for the C++ classes, complex APIs can be generated using IMake and SWIG.

Language Capabilities

To support a cross compatible API for three languages, a subset of language capabilities must be used.  Although this is somewhat limiting, it still provides a good platform for API development.

Multiple inheritance

Multiple inheritance is not supported since .Net and Java do not support it.

Templates

They are not supported in any of the target languages so they should not be used in any publicly accessible API.  However, template classes can be used internally by the C++ classes.

Pass by value

The semantics are different in each language.  For compatibility, all scalar types are pass by value.  Pass by value is not supported for objects.

Pass by reference

Since the semantics are different for each language, only objects are passed by reference.  Scalar types are not.

Out parameters

Out parameters are not universally supported so they should not appear in the public API.  Methods that need to return information should use the return value for the method.  Since objects are passed by reference, it is possible to modify an input argument if it is an object.  However, this should be avoided since the API can be serialized across application tiers and input argument modifications are not supported across tiers.  Just use the return value.  Your life will be easier.

Nested classes

Nested classes are difficult to support in each language and should be avoided

Exception handling

Exceptions are fully supported and should be used extensively to report error conditions.

Variable arguments

Variable arguments are handled differently in each language and are difficult to support in a cross-language fashion.  They are not supported.

Static Members

Static members are not well supported by SWIG and should not be used.

Namespaces

PHP does not support namespaces so exported classes should not belong to a namespace.

Enumerations

Enumerations should not be used.  However, a C++ classe which holds constants can be used.  IMake will generate appropriate classes in each of the three languages from this C++ constants class.

Object Lifetime

Object lifetime requires careful consideration for a cross platform API.  The C++ code is the source of the original object and each PHP, .Net, and Java wrapper class has a pointer to the C++ object.  C++ objects may have cross references to each other in addition to the reference from the wrappers.  To handle these complicated lifetimes, a reference counting mechanism is implemented in C++.

Each wrapper class holds a single reference count to its C++ object.  When the wrapper is destroyed, the C++ reference count is decremented and the C++ object deleted if the reference count is zero.  This ensures that the C++ objects remain alive as long as they are needed.

Data Types

To support cross-language portability, a set of common scalar types are used.  These include booleans, 16/32/64 bit signed integers, single precision floats, double precision floats, and Unicode compliant strings.  Note:  Unsigned integers are not supported since they are not supported by the Microsoft CLR.

Language Constructs

Supported Data Types

Integral types

 

C++

PHP

C#

Java

INT8

(*) int

Byte

byte

INT16

(*) int

Int16

short

INT32

(*) int

Int32

int

INT64

string

Int64

Int64

(*) there is no int keyword in PHP. Types are implicit but PHP internally supports integral values.

Notes:

    • There is only one integer type in PHP, equivalent to the C++ ‘int’, so the caller must ensure that the value passed to an interface function is in the appropriate range.
    • PHP does not have a 64 bit integral type.  However, various PHP functions that manipulate 64 bit integers take strings as parameters.  Therefore, 64 bit integers are converted to strings for the PHP API.
    • In C#, the System types, rather than their alias (such as short, int), since they clearly express the size of the data. Primitive types are just aliases so there is no performance penalty in doing so.

 

Boolean type

 

C++

PHP

C#

Java

bool

(*) bool

Boolean

bool

(*) there is no int keyword in PHP. Types are implicit, but PHP internally supports boolean values

Floating point types

 

C++

PHP

C#

Java

float

(*) double

Single

float

double

(*) double

Double

double

(*) there is no double keyword in PHP. Types are implicit, but PHP internally supports double floating point values.

Notes:

- PHP has only one ‘double’ type, equivalent to the C++ ‘double’. It is the responsibility of the caller to ensure that no truncation will occur when a ‘float’ value is required.

Strings

 

C++

PHP

C#

Java

STRING

(*) string

String

String

(*) there is no string keyword in PHP. Types are implicit, but PHP supports internally string values.

Notes:

    • In the C++ code, STRING is defined as wstring which is UTF-16 on Windows and UTF-32 on Linux.
    • The PHP mbstring library should be loaded to support Unicode strings.  UTF-8 should be set as the internal coding forming.
    • In C# and Java, strings are stored as Unicode (UTF-16)
    • Marshalling and conversion to/from the various languages is based on the above assumptions.

 

Objects

Objects are supported in the 3 target languages.  All objects are passed by reference in the API.  The object references should be considered “in” parameters only since the API supports multiple tiers.

Byte arrays

 

C++

PHP

C#

Java

char*

(*) byte array

Byte[]

byte[]

(*) In PHP there is no syntax to declare explicitely a byte array, but they are supported internally

Notes:

    • Byte array is the only array type supported as a fundamental type.

 

Date Time

The MgDateTime class is used to supports date and time objects in the API.  The native date and time objects in PHP, .Net and Java are not directly supported.

3.1.8 Streams

Language native streams are not supported by the API.  However, the MgByteReader class is used to implement a one-time read-only “stream” which is consistent for all of the APIs.  MgByteReader should be used for any streaming, large dataset, or “blob” input parameters and return values.

Method and Object scope

C++ Objects should be declared outside of any namespaces.  Three C++ defines are used in place of the C++ standard scope declarations to determine the exportability of an API method.  IMake, SWIG, and Doxygen use these defines while processing.

C++ define

C++ scope

Exported via SWIG

Documented

PUBLISHED_API

public:

Yes

Yes

EXTERNAL_API

public:

Yes

No

INTERNAL_API

public:

No

No

PUBLISHED_API should be used for “officially” supported and documented API methods.  Published API methods should remain consistent from release to release but can be deprecated over time.

EXTERNAL_API should be used for undocumented and unsupported methods which are exposed via SWIG.  These methods may not be consistent from release to release and may change without warning.

INTERNAL_API should be used for any public C++ methods that are not exposed through SWIG.  They are not documented and can change from release to release.  “Internal” methods are the inner workings of the API.

Constructors

Overloaded constructors are supported.  All variables should be initialized appropriately.  To support Linux compilation under gcc, all objects must include a default (no arguments) constructor.

Destructors and Object Pointers

Destructors should be declared private and virtual.  All API objects must derive from MgDisposable and implement the Dispose() method.  Dispose() is typically coded as “delete this”.  API object lifetime is managed using a reference counting mechanism.  The Ptr<> template class provides automatic, scope based reference count management and should be used in place of raw object pointers.

Method Signatures

Pointer references “*” should only be used on objects.  References “&” should never be used on any PUBLISHED_API or EXTERNAL_API.  INTERNAL_API methods have no restrictions.

Exceptions

Exceptions are fully supported.  Exceptions should derive from MgException, or one of its derived classes.  The exception text is looked up in the string based on the name of the exception class.  For example, the MgLogicException class has a string resource named “MgLogicException”.

Registration

API classes should be registered.  The class declaration should contain a CLASS_ID: section with a static const INT32 m_cls_id variable and implement GetClassId().  Each class can be registered by using MgClassFactory::GetInstance() object at application startup.  MgService derived objects should be registered using the MgServiceRegistry::GetInstance() object.  The following macros should be used to support registration and dynamic construction of objects:  DECLARE_CREATE_OBJECT(), DECLARE_CLASSNAME(), IMPLEMENT_DYNCREATE()

Serialization

All classes referenced in PUBLISHED_API and EXTERNAL_API methods of MgService derived objects must be registered.  These classes are serialized as they move between the Web Extensions and the Server.  To support the serialization mechanism, the classes should derive from MgSerializable and implement the Serialize() and Deserialize() methods.