We use proprietary and third party's cookies to improve your experience and our services, identifying your Internet Browsing preferences on our website; develop analytic activities and display advertising based on your preferences. If you keep browsing, you accept its use. You can get more information on our Cookie Policy
Cookies Policy
Middleware Advanced Open API Specification - FIWARE Forge Wiki

Middleware Advanced Open API Specification

From FIWARE Forge Wiki

Jump to: navigation, search

Contents

Introduction

The Middleware GE framework realizes a novel approach for connecting application code with a framework. The current version supports remote procedure calls (RPC) but support for publish/subscribe communication is planned as well.

Instead of forcing an application to use data types predefined by the middleware (as e.g. in Thrift, CORBA, etc.) applications describes its own native data types to the Middleware GE framework. The middleware then generates the necessary code to access those data structures at run-time using an embedded compiler. Due to the negotiation of the optimal protocol for the targeted server, Middleware GE can generate the optimal code to serialize the native data structures for the chosen protocol.

This approach allows to combine Middleware GE with arbitrary applications without rewriting major parts of the application itself or adding redundant copy operations between the native and middleware generated data structures, as required by other middleware.

This specifications describes C and C++ language support of the Middleware GE.

Name prefixes

Because C has no namespace system, all Middleware GE specific functions, macros, constants and commands need a dedicated prefix to distinguish them from the application functions, macros, constants and commands. In this specification the prefix mw or MW is used for all of the middleware components. The concrete Middleware GE implementations will then have to use their own specific prefix instead of this generic one.

Supported types and language constructs

Following an overview of the supported types and language constructs.

IDL Types

The interface definition language (IDL) is used to describe interfaces of the services provided at the remote endpoint, where remote endpoint not necessarily means a different host. The service could even exist as a plug-in within the same application. IDL uses data types and structures, but is platform-, architecture-, and application-agnostic.

The Middleware GE IDL types are based on the Thrift types: http://thrift.apache.org/docs/types/. These types are distinct from native application types and used solely to describe abstract service interfaces. For the detail specification of the Middleware GE IDL see chapter Interface Definition Language.

Primitive types

The Middleware GE provides following primitive types:

  • boolean: A boolean value (true or false)
  • i8: An 8-bit signed integer
  • u8: An 8-bit unsigned integer
  • i16: A 16-bit signed integer
  • u16: A 16-bit unsigned integer
  • i32: A 32-bit signed integer
  • u32: A 32-bit unsigned integer
  • i64: A 64-bit signed integer
  • u64: A 64-bit unsigned integer
  • float: A 32-bit floating point number
  • double: A 64-bit floating point number
  • string: A text string encoded using UTF-8 encoding

In the following table the Middleware GE types are compared with types used in other systems:

Middleware GE CORBA .NET Thrift SOAP WebIDL Range
boolean boolean Boolean bool boolean boolean {true, false}
i8 SByte byte byte byte [-128, 127]
u8 octet Byte unsignedByte octet [0, 255]
i16 short Int16 i16 short short [-32768, 32767]
u16 unsigned short UInt16 unsignedShort unsigned short [0, 65535]
i32 long Int32 i32 int long [-2147483648, 2147483647]
u32 unsigned long UInt32 unsignedInt unsigned long [0, 4294967295]
i64 long long Int64 i64 long long long [-9223372036854775808, 9223372036854775807]
u64 unsigned long long UInt64 unsignedLong unsigned long long [0, 18446744073709551615]
float float Single float float float http://en.wikipedia.org/wiki/Single_precision
double double Double double double double http://en.wikipedia.org/wiki/Double-precision_floating-point_format
string string String string string DOMString

Structs

The Middleware GE structs define a derived type that represent a collection of members similar to C/C++'s struct. A struct has a set of strongly typed fields, each with a unique name identifier. Fields may have various annotations (numeric field IDs, optional default values, etc.) that are described in the Middleware GE IDL.

Containers

The Middleware GE supports following container types:

  • array< Type, Size > - fixed length arrays (e.g. array< i32, 4 >)
  • array< Type > - dynamic arrays

Multidimensional arrays can be constructed by combining multiple array's: 4x4 float array - array< array< float, 4>, 4>

Exceptions

Exceptions are functionally equivalent to structs and are thrown when service functions fails.

Services

Services are similar to structs, but contain only functions.

Functions

Functions can only be members of services. Each function has return type, list of named arguments, and list of named exceptions. Function, its arguments and exceptions can be annotated. Functions can have void type as a return type.

Annotations

Annotations are functionally equivalent to structs but are only used to represent additional information to the IDL.

Describing Application Data Types, Functions and Services

In order to connect remote services, abstract IDL types described above need to be mapped to the application-specific data types. The Middleware GE API provides a novel method to perform this task in a non-intrusive way. Instead of forcing to use predefined data types of the middleware (e.g. Thrift, CORBA, etc.) applications describes their own data types. This allows to combine the Middleware GE with arbitrary applications without rewriting major parts of the application itself.

Application data types of C/C++ application are described using macros which create static in memory representation of the data types. Macros are used as they provide a type-safe way of connecting the type descriptions to the data structures they describe such that changes will automatically detected. These macros mostly declare static data structures than are later references when interface functions and services are declared. Macros can be written by hand, which can be somewhat tedious especially for C language, or generated automatically by a preprocessor tool.

Using the preprocessor tool

An mw-preprocessor tool should be able to create the applications type descriptions to be used with the Middleware GE framework. Using the application source code and a number of simple annotations that describe which types should be used by the Middleware GE, the tool should create the required macros. These can be added directly into the source code or provided as a separate header file.

In the following sections we describe how these annotations and macros are used to create the application type and function description.

Describing primitive data types

In our example we will use the following IDL definition:

namespace * calc
service calc {
    i32 add(i32 a, i32 b)
    float addf(float a, float b)
    i32 stringToInt32(string s)
    string int32ToString(i32 i)
}

For the client it is required to generate functions that will perform a remote call and connect them with the remote methods of the service:

mw_declare_func(Calc_Add, int & result_value result, int a, int b)
mw_declare_func(Calc_Add_Float, float & result_value result, float a, float b)
mw_declare_func(Calc_String_To_Int32, int & result_value result, const char *s)
mw_declare_func(Calc_Int32_To_String, char ** result_value result, int i)

mw_declare_func(type_name, ...) macro declares remote function that can be called by the client. All arguments after the function name are argument types and names to the function. result_value macro can be used between type and name argument for describing arguments that are received as a result of the call (output arguments). Note that result_value can only be applied to a pointer or reference type, which can receive a result value.

For the server it is required to generate service functions that will be called upon remote call:

mw_declare_service(Calc_Add_Impl, int & result_value result, int a, int b)
mw_declare_service(Calc_Add_Float_Impl, float & result_value result, float a, float b)
mw_declare_service(Calc_String_To_Int32_Impl, int & result_value result, const char *s)
mw_declare_service(Calc_Int32_To_String_Impl, char ** result_value result, int i)


mw_declare_service(type_name, ...) macro is similar to the mw_declare_func and declares service function that can be called by the client. All arguments after the function name are argument types and names to the function.

Running the mw-preprocessor tool on the source code with these macros will generate Middleware GE API macros that will describe not just the client and server function but also generates macros for all involved data types.

Describing complex data types

The above described approach works also for C/C++ structs as long as they contain combination of structs and primitive data types like int, float, etc. More complex data types require additional annotation since the preprocessor cannot extract the necessary application semantics automatically (C/C++ are low-level languages that do not provide all the needed semantics implicitly). Such types can be described by providing custom user functions which will access the internals of a type. We call such types opaque.

For example C++ std::string can be mapped to IDL string object, but in order to do this the Middleware GE need to know how to get and set internal character sequence:

int std_string_SetCString(MW_UserType *ustr, const char *cstr)
{
    if (cstr)
        ((std::string*)ustr)->assign(cstr);
    else
        ((std::string*)ustr)->clear();
    return 0;
}
 
int std_string_GetCString(MW_UserType *ustr, const char **cstr)
{
    *cstr = ((std::string*)ustr)->c_str();
    return 0;
}
 
mw_declare_opaque_object(std::string,
                            mw_user_api(SetCString, std_string_SetCString),
                            mw_user_api(GetCString, std_string_GetCString))

mw_declare_opaque_object(type_name, user_api_entries) macro associates multiple custom user functions with API known to Middleware GE. In the above case it registers std_string_GetCString as a getter and std_string_SetCString as a setter functions which return and set std::string contents as a C's null-terminated string respectively. SetCString and GetCString are reserved Middleware GE keywords that describe an API functions that the user implements.

With this additional declaration it is possible to use std::string when calling and implementing service methods stringToInt32 and int32ToString:

// for the client
mw_declare_func(Calc_StdString_To_Int32, int & result_value result, const std::string & s)
mw_declare_func(Calc_Int32_To_StdString, std::string & result_value result, int i)
 
// for the server
mw_declare_service(Calc_StdString_To_Int32_Impl, int & result_value result, const std::string & s)
mw_declare_service(Calc_Int32_To_StdString_Impl, std::string & result_value result, int i)

Besides manipulating contents of opaque types the Middleware GE needs to properly allocate and deallocate them:

MW_UserType * std_string_Allocate(void)
{
    return (MW_UserType *)new std::string;
}
 
void std_string_Deallocate(MW_UserType *value)
{
    delete (std::string*)value;
}
 
mw_declare_opaque_object(std::string,
                            mw_user_api(SetCString, std_string_SetCString),
                            mw_user_api(GetCString, std_string_GetCString),
                            mw_user_api(AllocateType, std_string_Allocate),
                            mw_user_api(DeallocateType, std_string_Deallocate))

AllocateType and DeallocateType represent APIs for allocating and deallocating type respectively.

Array members in structure declaration

Besides opaque types it may be required to describe more complex structure members like C-arrays. They are often defined in C and sometimes in C++ structs as a combination of an integer member representing array size and a pointer member pointing to the first element of the array. For example:

typedef struct IntArray
{
    int size;
    int *array;
} IntArray;


Such member combination can be described with the preprocessor macro mw_struct_array_member:

/* Middleware GE declaration of IntArray */
 
mw_declare_struct(IntArray,
    mw_struct_array_member(array, size))

The mw_declare_struct(type_name, ...) macro allows to override a type declaration which usually would otherwise be automatically generated.

Static type construction

The preprocessor described above generates source code containing Middleware GE API macros that need to be included in the application part that uses the Middleware GE API for calling services. These macros can be also written manually or maybe even generated by another tool. In this section we will describe these macros in more detail.

Primitive types

For declaring C-type for Middleware GE it must have a name, for example by defining it with a typedef. The Middleware GE predefines names for all primitive C types (Remark: Concrete implementations will replace the MW_ prefix with their own):

Middleware GE macro C type
MW_CHAR char
MW_WCHAR_T wchar_t
MW_SCHAR signed char
MW_UCHAR unsigned char
MW_SHORT short
MW_USHORT unsigned short
MW_INT int
MW_UINT unsigned int
MW_LONG long
MW_ULONG unsigned long
MW_LONGLONG long long
MW_ULONGLONG unsigned long long
MW_SIZE_T size_t
MW_SSIZE_T ssize_t
MW_VOID void
MW_FLOAT float
MW_DOUBLE double
MW_LONGDOUBLE long double
MW_CHAR_PTR char *
MW_VOID_PTR void *
MW_INT8_T int8_t
MW_UINT8_T uint8_t
MW_INT16_T int16_t
MW_UINT16_T uint16_t
MW_INT32_T int32_t
MW_UINT32_T uint32_t
MW_INT64_T int64_t
MW_UINT64_T uint64_t


Note: this list contains all primitive types that can be described with Middleware GE, not all of them can be directly mapped to the IDL types. See Calling remote functions section for supported mappings.

Pointer declaration

The Middleware GE supports pointer declaration with the MW_DECL_PTR macro.

/* Note: MW_INT and MW_FLOAT are predefined, float* is not */
typedef float * FloatPtr;
 
/* int pointer */
MW_DECL_PTR(IntPtr, MW_INT)
 
/* float pointer */
MW_DECL_PTR(FloatPtr, MW_FLOAT)
 
/* FloatPtr* */
MW_DECL_PTR(FloatPtrPtr, FloatPtr)

MW_DECL_PTR(ptr_type_name, element_type_name) macro declare a pointer type for the existing named C-type specified as the second argument. Const pointers are declared with MW_DECL_CONST_PTR macro:

/* const int pointer */
MW_DECL_CONST_PTR(ConstIntPtr, MW_INT)

In C++ pointer declaration is not needed.

Structure declaration

Structures are declared with the MW_DECL_STRUCT macro:

/* User-defined C struct */
 
typedef struct {
    float x;
    float y;
} Vec2f;
 
/* Middleware GE declaration of Vec2f */
 
MW_DECL_STRUCT(Vec2f,
  MW_STRUCT_MEMBER(MW_FLOAT, x)
  MW_STRUCT_MEMBER(MW_FLOAT, y)
)
 
/* User-defined C struct */
 
typedef struct {
    Vec2f a;
    Vec2f b;
} Linef;
 
/* Middleware GE declaration of Linef */
 
MW_DECL_STRUCT(Linef,
  MW_STRUCT_MEMBER(Vec2f, a)
  MW_STRUCT_MEMBER(Vec2f, b)
)

MW_DECL_STRUCT(type_name, members) macro expects two arguments: name of a C struct type and a sequence of MW_STRUCT_MEMBER macros. All Middleware GE macros require that passed type names are typedef names, not tag names. MW_STRUCT_MEMBER(member_type_name, member_name) macro expects also two arguments describing struct member: name of a member type and name of a member. Note that sequence is not separated by comma. Also it is allowed to omit members in the described structure.

The type name passed to MW_STRUCT_MEMBER macro must either be defined previously by one of the Middleware GE declaration macros or be one of predefined for primitive C types.

When using C++ alternative macros MW_CXX_DECL_STRUCT and MW_CXX_STRUCT_MEMBER can be used instead:

/* Middleware GE declaration of Vec2f */
 
MW_CXX_DECL_STRUCT(Vec2f,
  MW_CXX_STRUCT_MEMBER(x)
  MW_CXX_STRUCT_MEMBER(y)
)
 
/* Middleware GE declaration of Linef */
 
MW_CXX_DECL_STRUCT(Linef,
  MW_CXX_STRUCT_MEMBER(a)
  MW_CXX_STRUCT_MEMBER(b)
)

Using C++ templates the macro MW_CXX_DECL_STRUCT should deduce types of the struct members automatically.

Array members in structure declaration

Arrays are often defined in C and sometimes in C++ structs as a combination of an integer member representing array size and a pointer member pointing to the first element of the array. For example:

typedef struct IntArray
{
    int size;
    int *array;
} IntArray;

Such member combination can be described with the Middleware GE macro MW_STRUCT_ARRAY_MEMBER:

/* Middleware GE declaration of IntArray */
 
MW_DECL_PTR(IntPtr, MW_INT)
 
MW_DECL_STRUCT(IntArray,
  MW_STRUCT_ARRAY_MEMBER(IntPtr, array, MW_INT, size)
)

MW_STRUCT_ARRAY_MEMBER(ptr_member_type_name, ptr_member_name, size_member_type_name, size_member_name) macro expects also four arguments describing array member: name of a pointer type, name of a pointer member, name of a size type, and name of a size member.

The type name passed to MW_STRUCT_ARRAY_MEMBER macro must either be defined previously by one of the Middleware GE declaration macros or be one of predefined for primitive C types.

Arrays are allocated by default with malloc and deallocated with free C function calls.

When using C++ the alternative macro MW_CXX_STRUCT_ARRAY_MEMBER can be used instead:

/* Middleware GE declaration of IntArray */
 
MW_CXX_DECL_STRUCT(IntArray,
  MW_CXX_STRUCT_ARRAY_MEMBER(array, size)
)

Using C++ templates the macro MW_CXX_STRUCT_ARRAY_MEMBER can deduce types of the struct members automatically.

Function declaration

Signatures of functions that are generated by Middleware GE are declared with the MW_DECL_FUNC macro:

/* Middleware GE declaration of a float* type */
MW_DECL_PTR(FloatPtr, MW_FLOAT)
 
/* Middleware GE declaration of a function with signature:
 
   typedef int (*Func)(float *result, int a, float b, double c);
 */
 
MW_DECL_FUNC(Func,
  MW_FUNC_RESULT(FloatPtr, result)
  MW_FUNC_ARG(MW_INT, a)
  MW_FUNC_ARG(MW_FLOAT, b)
  MW_FUNC_ARG(MW_DOUBLE, c)
)

MW_DECL_FUNC(type_name, func_args) macro is similar to MW_DECL_STRUCT and expects two arguments: name of a C function type and sequence of MW_FUNC_ARG and MW_FUNC_RESULT macros. MW_FUNC_ARG(type_name, member_name) macro expects two arguments describing function argument: name of an argument type and a name of an argument. MW_FUNC_RESULT(type_name, member_name) does the same as MW_FUNC_ARG, but additionally marks the function parameter as a result of a function call. The return type of the declared function signature is always int and is used to report errors. Note that sequence is not separated by comma. The type name passed to MW_FUNC_ARG and MW_FUNC_RESULT macros must either be defined previously by one of the Middleware GE declaration macros or be one of the predefined for primitive C types (see above).

Similar to structs functions can be declared in C++ with MW_CXX_DECL_FUNC, MW_CXX_FUNC_ARG, and MW_CXX_FUNC_RESULT:

/* Middleware GE declaration of Func */
 
MW_CXX_DECL_FUNC(Func,
  MW_CXX_FUNC_RESULT(float *, result)
  MW_CXX_FUNC_ARG(int, a)
  MW_FUNC_ARG(float, b)
  MW_FUNC_ARG(double, c)
)

In C++ derived types of function parameters like pointers and references do not need to be explicitly declared.

Array declaration

Arrays are declared with MW_DECL_ARRAY, MW_DECL_FIXED_ARRAY, and MW_DECL_FIXED_ARRAY_2D macros.

typedef int IntArray[];
typedef float FloatArray4[4];
typedef double DoubleMatrix4[4][4];
 
/* Unbounded array declaration */
 
MW_DECL_ARRAY(IntArray, MW_INT)
 
/* Fixed size 1D array declaration */
 
MW_DECL_FIXED_ARRAY(FloatArray4, MW_FLOAT, 4)
 
/* Fixed size 2D array declaration */
 
MW_DECL_FIXED_ARRAY_2D(mat44, MW_FLOAT, 4, 4)

MW_DECL_ARRAY(array_type_name, element_type_name) macro declares one-dimensional unbounded arrays. MW_DECL_FIXED_ARRAY(array_type_name, element_type_name, size) macro declares one-dimensional arrays with fixed length. MW_DECL_FIXED_ARRAY_2D(array_type_name, element_type_name, num_rows, num_cols) macro declares two-dimensional arrays with fixed length.

All macros expect the name of the declared type as the first argument, previously declared name of the array element as the second, and finally dimension values.

In C++ array types do not need to be explicitly declared.

Forward declaration

The Middleware GE supports forward declaration of types which is required for declaring cyclic structures. The Middleware GE type is forward declared using the MW_FORWARD_DECL macro.

typedef struct IntList {
    struct IntList *next;
    int data;
} IntList;
 
/* Forward declaration of a named type */
MW_FORWARD_DECL(IntList)
 
/* Pointer declaration */
MW_DECL_PTR(IntListPtr, IntList)
 
MW_DECL_STRUCT(IntList,
  MW_STRUCT_MEMBER(IntListPtr, next)
  MW_STRUCT_MEMBER(MW_INT, data)
)

MW_FORWARD_DECL(type_name) macro performs a forward declaration of a specified type.

In C++ forward declaration is not needed.

Opaque types

Often data structures are part of the implementation and are not defined in a public API. Such data structures are called abstract data types (ADT). In C and C++ such data structures are represented by a declaration of a struct type without public definition. Such struct types are called opaque, and the only way to access their data is to use a public API provided along with the opaque type.

Data access

In the Middleware GE opaque type are defined with MW_DECL_OPAQUE_TYPE macro. In order to access its contents the Middleware GE needs to know how to get and set its internal data. The access is performed by getter and setter API functions which signature is known to the Middleware GE. These functions are registered with the opaque type by using the MW_USER_API macro. The Middleware GE recursively processes an opaque type by calling API function until it reaches a primitive type supported by default.

The following example shows how the kr_dstring_t abstract data type implements dynamic strings in C:

int dstring_SetCString(MW_UserType *ustr, const char *cstr)
{
    int result = kr_dstring_assign_str((kr_dstring_t*)ustr, cstr);
    return result ? 0 : 1;
}
 
int dstring_GetCString(MW_UserType *ustr, const char **cstr)
{
    *cstr = kr_dstring_str((kr_dstring_t*)ustr);
    return 0;
}
 
MW_DECL_OPAQUE_TYPE(kr_dstring_t,
       MW_USER_API(SetCString, dstring_SetCString)
       MW_USER_API(GetCString, dstring_GetCString))

In the above example contents of a kr_dstring_t type can be returned as a C-string and set to a C-string. Thus it implements and registers two API access functions named SetCString and GetCString for setting and getting C-strings from a kr_dstring_t type respectively.

With C++ API it is possible to describe the std::string type in a similar way:

static int std_string_SetCString(MW_UserType *ustr, const char *cstr)
{
    if (cstr)
        ((std::string*)ustr)->assign(cstr);
    else
        ((std::string*)ustr)->clear();
    return 0;
}
 
static int std_string_GetCString(MW_UserType *ustr, const char **cstr)
{
    *cstr = ((std::string*)ustr)->c_str();
    return 0;
}
 
MW_CXX_DECL_OPAQUE_TYPE(std::string,
       MW_CXX_USER_API(SetCString, std_string_SetCString)
       MW_CXX_USER_API(GetCString, std_string_GetCString))

Memory management

Besides accessing contents of an opaque type the Middleware GE needs to know how to allocate and deallocate a type. By default the Middleware GE allocates a type by calling C's malloc function. However, when type require additional initialization like in the case with std::string and kr_dstring_t, we need to register custom allocation and deallocation functions with the opaque type.

static MW_UserType * std_string_Allocate(void)
{
    return (MW_UserType *)new std::string;
}
 
void std_string_Deallocate(MW_UserType *value)
{
    delete (std::string*)value;
}
 
MW_CXX_DECL_OPAQUE_TYPE(std::string,
       MW_CXX_USER_API(SetCString, std_string_SetCString)
       MW_CXX_USER_API(GetCString, std_string_GetCString)
       MW_CXX_USER_API(AllocateType, std_string_Allocate)
       MW_CXX_USER_API(DeallocateType, std_string_Deallocate))

Structs with API

Not only opaque types, but usual structures may require custom access or allocation behavior. For example when members of a structure require additional initialization after allocation. For this case API functions can be registered for structs:

typedef struct Data
{
    int ival;
    kr_dstring_t sval;
} Data;
 
void initData(Data *data)
{
    data->ival = 0;
    kr_dstring_init(&data->sval);
}
 
void destroyData(Data *data)
{
    kr_dstring_destroy(&data->sval);
}
 
MW_UserType * Data_Allocate(void)
{
    Data *data = malloc(sizeof(Data));
    initData(data);
    return (MW_UserType *)data;
}
 
void Data_Deallocate(MW_UserType *value)
{
    if (value)
    {
        destroyData((Data*)value);
        free(value);
    }
}
 
MW_DECL_STRUCT_WITH_API(Data,
  MW_STRUCT_MEMBER(MW_INT, ival)
  MW_STRUCT_MEMBER(kr_dstring_t, sval),
  MW_USER_API(AllocateType, Data_Allocate)
  MW_USER_API(DeallocateType, Data_Deallocate)
)

The Data structure need to be initialized after allocation and uninitialized after deallocation. Thus it requires custom allocation and deallocation functions.

Service declaration

Similarly to functions generated by Middleware GE for calling remote services, a remote service function can be declared as well.

MW_DECL_SERVICE(Add,
    MW_SERVICE_RESULT(IntPtr, result)
    MW_SERVICE_ARG(MW_INT, a)
    MW_SERVICE_ARG(MW_INT, b))
{
    *result = a + b;
    return MW_SUCCESS;
}

MW_DECL_SERVICE(function_name, func_args) macro declares a function which can be called remotely, similarly to MW_DECL_FUNC macro.

Applications

The following descriptions shows how the Middleware GE will be used by an application.

Initialization and finalization

Before the Middleware GE library can be used it needs to be initialized with the call to the mwInit function. After usage all allocated resources should be freed by calling the mwFinalize function.

int main(int argc, char **argv)
{
    mwInit(&argc, argv);
 
    mwFinalize();
}


Contexts

The Middleware GE require that each thread has a separate context that manages all Middleware GE data structures. Having separate context per thread allows to avoid explicit locking by the user. Context is created with mwNewContext function and destroyed with mwFreeContext function.

int main(int argc, char **argv)
{
    MW_Context *ctx;
 
    /* Initialize Middleware GE */
    mwInit(&argc, argv);
 
    /* Create context */
    ctx = mwNewContext();
 
    /* Destroy context */
    mwFreeContext(ctx);
 
    /* Finalize Middleware GE */
    mwFinalize();
}

Most of Middleware GE API functions require this context as an argument.

Establishing connections

Before Middleware GE can call remote functions a connection to the remote Middleware GE node needs to be established with the call to mwOpenConnection function.

  MW_Connection *conn;
  MW_Context *ctx;
 
  /* Create context */
  ctx = mwNewContext();
  /* Open connection to the service */
  conn = mwOpenConnection(ctx, "http://myhost:80/service");

The URL used as the argument should refer to the service description which describes resources available at the endpoint of the connection. The service description has following structure:

{
    // Description of the server, optional
    info : "test server",
 
    // absolute or relative URL of the Middleware GE IDL
    idlURL : "/idl/calc.mw",
 
    // Middleware GE IDL contents as string (either idlURL or idlContents should be present)
    idlContents : "text",
 
    // List of servers providing services
    servers : [
        {
            // names of services that served with this configuration,
            // or "*" for all services
            services : "pattern",
            // specification of used marshalling protocol
            protocol : {
                name : "jsonrpc" // name of the protocol
            },
            // specification of used transport protocol
            transport : {
                // name of the transport protocol
                name : "http",
                // URL for URL-based transport
                url : "/rpc/calc"
            }
        },
        ...
    ]
}

In the process of establishing a connection the Middleware GE negotiates compatible protocols and transport methods and selects the best one supported by both Middleware GE nodes. Finally descriptions of services available on the remote side are fetched and added to the internal Middleware GE Type Description managed by Middleware GE.

The connection is closed by calling mwCloseConnection.

  mwCloseConnection(conn);

Calling remote functions

In order to call a remote function the Middleware GE generates a client stub function that accepts native application datatypes, serializes them to the format used by the current protocol, performs a call, and deserializes received response. For generating a client stub function MW_GENERATE_CLIENT_FUNC(connection, idl_method_name, func_type_name, mapping) macro is used. The arguments have the following meaning:

connection - valid MW_Connection handle opened with mwOpenConnection.
idl_method_name - name of the remote service method specified in the IDL.
func_type_name - name of the function object type declared with the MW_FUNC_OBJ(func_type_name) macro.
mapping - optional mapping of the IDL types to the application types. By default 1:1 mapping by names and types is used. Note: mapping is not implemented yet.

Consider following simple IDL:

namespace * calc
 
service calc {
    i32 add(i32 a, i32 b)
}

The IDL method calc.add can be mapped to the following function prototype:

MW_DECL_FUNC(Calc_Add,
  MW_FUNC_RESULT(IntPtr, result)
  MW_FUNC_ARG(MW_INT, a)
  MW_FUNC_ARG(MW_INT, b))

Basic types are mapped accordingly to the following table:

Middleware GE IDL Type Default native type (C99 types) Linux 32-bit C-Type
boolean int32_t int
i8 int8_t char
u8 uint8_t unsigned char
i16 int16_t short
u16 uint16_t unsigned short
i32 int32_t int
u32 uint32_t unsigned int
i64 int64_t long long
u64 uint64_t unsigned long long
float float float
double double double
string char * char *

Native types that are not in the table are mapped by converting them automatically to the required type.

The function instance performing call to the remote side is created in the following way:

MW_FUNC_OBJ(Calc_Add) add;
 
add = MW_GENERATE_CLIENT_FUNC(conn, "calc.add", Calc_Add, "");

The macro MW_FUNC_OBJ(type_name) defines function object type that can store stub instances of a given type generated by the MW_GENERATE_CLIENT_FUNC macro. The function object can be executed with the MW_CALL macro:

int result, errorCode;
errorCode = MW_CALL(add, &result, 21, 32);

Returned value is MW_SUCCESS on success and error code otherwise. Error code description can be retrieved by a call to the mwGetConnectionError function.

if (errorCode != MW_SUCCESS)
  fprintf(stderr, "Error: call failed: %s\n", mwGetConnectionError(conn));
else
  printf("calc.add: result = %i\n", result);

In C++ a less complex syntax for defining and calling functions can be used.

MW_CXX_DECL_FUNC(Calc_Add,
  MW_CXX_FUNC_RESULT(int &, result)
  MW_CXX_FUNC_ARG(int, a)
  MW_CXX_FUNC_ARG(int, b))

Since C++ provides references it is not required to pass address of a result variable when calling Calc_Add function:

int result;
Calc_Add add = MW_GENERATE_CLIENT_FUNC(conn, "calc.add", Calc_Add, "");
int errorCode = add(result, 21, 32);

Defining remote services

On the server side it is required to define service functions that can be called. First, a service type is defined similarly to the function declaration:

MW_DECL_SERVICE(Calc_Add,
    MW_SERVICE_RESULT(IntPtr, result)
    MW_SERVICE_ARG(MW_INT, a)
    MW_SERVICE_ARG(MW_INT, b))

This declares only a prototype of the service function. Implementation of the service function must have the same number and type for all arguments. Additionally first argument must be of type MW_ServiceFuncObj * and provides information about connection. Result type must be MW_Result.

MW_Result calc_add_impl(MW_ServiceFuncObj *mw_funcobj, int *result, int a, int b)
{
    *result = a + b;
    return MW_SUCCESS;
}

When there is only a single implementation of the service function, both declarations can be combined:

MW_DECL_SERVICE_IMPL(Calc_Add,
    MW_SERVICE_RESULT(IntPtr, result)
    MW_SERVICE_ARG(MW_INT, a)
    MW_SERVICE_ARG(MW_INT, b))
{
    *result = a + b;
    return MW_SUCCESS;
}

When calling service functions the Middleware GE will automatically allocate memory, deserialize input, and serialize output parameters.

After defining service functions we need to create services, load IDL, and register service functions with the IDL methods:

MW_Context *ctx;
MW_Connection *conn;
MW_Service *service;
MW_Result result;
 
/* Create context */
ctx = mwNewContext();
 
/* Create service */
service = mwNewService(ctx);
 
/* Load and register IDL with the service */
result = mwLoadServiceIDLFromString(service,
    "MW",
    "namespace * calc "
    "service calc { "
    "    i32 add(i32 a, i32 b) "
    "} ");
 
/* Register calc.add IDL method with the Calc_Add service function */
result = MW_REGISTER_SERVICE_FUNC(service, "calc.add", Calc_Add, "", calc_add_impl);

mwLoadServiceIDLFromString function parses IDL from string and registers it with the service, alternatively IDL can be loaded from file with mwLoadServiceIDL function. The first argument to mwLoadServiceIDLFromString is the service handle, then name of the IDL language, and finally string with the IDL.

MW_REGISTER_SERVICE_FUNC macro is similar to MW_GENERATE_CLIENT_FUNC macro, but just registers service function with the IDL method, specified by its full name. Internally Middleware GE will generate code that deserializes arguments, calls registered function, and finally serializes result and send it back to the caller. When combined declaration is used MW_REGISTER_SERVICE_IMPL needs to be used instead of MW_REGISTER_SERVICE_FUNC.

Finally it is required to create server waiting for incoming connections, add all services that server should provide and run it.

server = mwNewServer(ctx, "0.0.0.0", "8080", "/service");
 
mwAddService(server, "/rpc/calc", "jsonrpc", service);
 
mwRunServer(server);
 
mwFreeServer(server);
 
mwFreeService(service);

mwNewServer function accepts host and port where connections will be accepted. Additionally an URL path with the location of the server configuration document is passed.

After server is created arbitrary number of services that should be served can be added with mwAddService function. Finally server is started with the mwRunServer function.

Examples

IDL

The following IDL is used for the examples:

namespace * aostest
 
struct Vec3f {
 float x,
 float y,
 float z
}
 
struct Quatf {
 float r,
 Vec3f v
}
 
struct Location {
 Vec3f position,
 Quatf rotation
}
 
struct LocationList {
 array<Location> locations
}
 
service aostest {
  void setLocations(LocationList locations);
  LocationList getLocations();
}

Common Source Code

aostest_types.h - common data structures independent of Middleware GE framework

/*
 * This file contains application code and data structures independent of the Middleware GE framework
 */
 
#ifndef AOSTEST_TYPES_H_INCLUDED
#define AOSTEST_TYPES_H_INCLUDED
 
#include <MW/mw.h>
#include <string.h>
 
#ifdef __cplusplus
extern "C" {
#endif
 
/* Location */
 
typedef struct Vec3f {
    float x;
    float y;
    float z;
} Vec3f;
 
typedef struct Quatf {
    float r; /* real part */
    Vec3f v; /* imaginary vector */
} Quatf;
 
typedef struct Location {
    Vec3f position;
    Quatf rotation;
} Location;
 
/* Data */
 
typedef struct LocationList
{
    int num_locations;
    Location *locations;
} LocationList;
 
static void initLocationList(LocationList *loclist, size_t size)
{
    loclist->num_locations = size;
    if (size == 0)
    {
        loclist->locations = NULL;
    }
    else
    {
        loclist->locations = malloc(sizeof(loclist->locations[0])*size);
    }
}
 
static void destroyLocationList(LocationList *loclist)
{
    loclist->num_locations = 0;
    free(loclist->locations);
    loclist->locations = NULL;
}
 
static void copyLocationList(LocationList *dest, const LocationList *src)
{
    if (dest->num_locations != src->num_locations)
    {
        destroyLocationList(dest);
        initLocationList(dest, src->num_locations);
    }
    memcpy(dest->locations, src->locations, sizeof(src->locations[0]) * src->num_locations);
}
 
static MW_UserType * LocationList_Allocate(void)
{
    LocationList *loclist = malloc(sizeof(LocationList));
    initLocationList(loclist, 0);
    return (MW_UserType *)loclist;
}
 
static void LocationList_Deallocate(MW_UserType *value)
{
    if (value)
    {
        destroyLocationList((LocationList*)value);
        free(value);
    }
}
 
#ifdef __cplusplus
}
#endif
 
#endif

Client example

aostest.c - client implementation of aostest (Array-Of-Structures Test) example

/*
 * This file contains client implementation of aostest (Array-Of-Structures Test) example
 * implemented in C with Middleware GE framework.
 */
 
#include <MW/mw.h>
#include <MW/mw_macros.h>
#include <MW/mw_pp_annotation.h>
 
#include "aostest_types.h"
#include "aostest_mw_client.h"
 
#include <stdio.h>
#include <string.h>
#include "c99fmt.h"
 
/*
 * Declare application structures and client functions of the application.
 * Following macros are processed by mw-preprocessor tool and result
 * is output to the aostest_mw_client.h file.
 */
 
/* mw_declare_struct_with_api(type_name, ...)
 *
 * Declares non-trivial structure type type_name, which require custom behavior via
 * user specified API functions.
 *
 * mw_struct_array_member(ptr_member_name, size_member_name)
 *
 * Declares member in a structure that represents a C-array composed from
 * pointer to the array field and integer array size field.
 *
 * mw_user_api(api_name, user_function_name)
 *
 * Registers user-defined API function user_function_name with a predefined Middleware GE API api_name
 *
 * In our example we need to declare LocationList structure explicitly because it
 * contains C-array composed from locations pointer and num_locations integer and
 * because LocationList requires custom allocation and deallocation functions.
 * Both can't be deduced automatically from the source code.
 */
mw_declare_struct_with_api(LocationList,
    mw_struct_array_member(locations, num_locations),
    mw_user_api(AllocateType, LocationList_Allocate),
    mw_user_api(DeallocateType, LocationList_Deallocate))
 
/* mw_declare_func(func_name, ...)
 *
 * Declares remote function that can be called by the client.
 * All arguments after the function name are argument types and names
 * to the function.
 *
 * Usually types used as arguments do not need to be explicitly declared.
 * Only when types require custom handling an explicit declaration is required.
 */
mw_declare_func(AOSTest_SetLocations, const LocationList * locations)
mw_declare_func(AOSTest_GetLocations, LocationList * result_value locations)
 
/*
 * Middleware GE context and connection variables.
 *
 * MW_Context is used for all Middleware GE operations issued from the single thread.
 * Each separate thread require a separate MW_Context instance.
 *
 * MW_Connection is a handle to the remote endpoint
 * over which remote calls are performed.
 */
MW_Context *ctx;
MW_Connection *conn;
 
/*
 * set_locations and get_locations are handles to the function objects
 * that perform remote call.
 * They are dynamically generated by the MW_GENERATE_CLIENT_FUNC macro.
 */
MW_FUNC_OBJ(AOSTest_SetLocations) set_locations;
MW_FUNC_OBJ(AOSTest_GetLocations) get_locations;
 
/*
 * Initialization of the connection
 */
void initConn(const char *url)
{
    /* Create new context */
 
    ctx = mwNewContext();
 
    /* Open connection to the service */
 
    printf("Opening connection to %s...\n", url);
    conn = mwOpenConnection(ctx, url);
 
    if (!conn)
    {
        fprintf(stderr, "Error: Could not open connection : %s\n", mwGetContextError(ctx));
        exit(1);
    }
 
    /*
     * MW_GENERATE_CLIENT_FUNC(connection, idl_method_name, func_type_name, mapping)
     *
     * Generates function that will perform a remote call.
     *
     * connection       - opened and valid MW_Connection handle.
     * idl_method_name  - name of the remote service method specified in the IDL.
     * func_type_name   - name of the function object type declared
     *                    with the MW_FUNC_OBJ(func_type_name) macro.
     * mapping          - mapping of the IDL types to the application types.
     *                    By default 1:1 mapping by names and types is used.
     *                    Note: mapping is not implemented yet.
     *
     * Note: The IDL of the server application is embedded in aostest_server.c.
     */
 
    set_locations = MW_GENERATE_CLIENT_FUNC(conn, "aostest.setLocations", AOSTest_SetLocations, "");
    if (!set_locations)
        fprintf(stderr, "Error: code generation failed: %s\n", mwGetConnectionError(conn));
 
    get_locations = MW_GENERATE_CLIENT_FUNC(conn, "aostest.getLocations", AOSTest_GetLocations, "");
    if (!get_locations)
        fprintf(stderr, "Error: code generation failed: %s\n", mwGetConnectionError(conn));
}
 
/*
 * Close connection and finalize Middleware GE framework
 */
void finalizeConn()
{
    mwCloseConnection(conn);
    mwFreeContext(ctx);
    mwFinalize();
}
 
int main(int argc, char **argv)
{
    const char *url = NULL;
    int errorCode;
 
    /* Initialize Middleware GE */
    mwInit(&argc, argv);
 
    if (argc > 1)
        url = argv[1];
    else
        url = "http://localhost:8080/service";
 
    /* Initialize connection and generate functions */
    initConn(url);
 
    /* Call remote functions */
 
    /* Send 10 locations to the server, where they will be stored */
    {
        size_t num, i;
        LocationList loclist;
        num = 10;
        initLocationList(&loclist, num);
        for (i = 0; i < num; ++i)
        {
            loclist.locations[i].position.x = i;
            loclist.locations[i].position.y = i;
            loclist.locations[i].position.z = i;
 
            loclist.locations[i].rotation.r = 0.707107f;
            loclist.locations[i].rotation.v.x = 0.0f;
            loclist.locations[i].rotation.v.y = 0.0f;
            loclist.locations[i].rotation.v.z = 0.70710701f;
        }
 
        /*
         * MW_CALL(funcobj, ...)
         *
         * Will call remote function via function object generated by MW_GENERATE_CLIENT_FUNC macro.
         * All arguments after function objects are input/output arguments to the remote function.
         * MW_CALL returns integer value of type MW_Result that represent an error code.
         */
 
        errorCode = MW_CALL(set_locations, &loclist);
        if (errorCode != MW_SUCCESS)
            fprintf(stderr, "Error: call failed: %s\n", mwGetConnectionError(conn));
        else
            printf("aostest.setLocations: DONE\n");
        destroyLocationList(&loclist);
    }
 
    /* Receive locations stored on the server, and print them */
    {
        size_t num, i;
        LocationList loclist;
 
        initLocationList(&loclist, 0);
 
        errorCode = MW_CALL(get_locations, &loclist);
        if (errorCode != MW_SUCCESS)
            fprintf(stderr, "Error: call failed: %s\n", mwGetConnectionError(conn));
        else
        {
            printf("aostest.getLocations: LocationList {\n");
            printf("  locations: [\n");
            for (i = 0; i < loclist.num_locations; ++i)
            {
                printf("    position %f %f %f rotation %f %f %f %f\n",
                    loclist.locations[i].position.x,
                    loclist.locations[i].position.y,
                    loclist.locations[i].position.z,
                    loclist.locations[i].rotation.r,
                    loclist.locations[i].rotation.v.x,
                    loclist.locations[i].rotation.v.y,
                    loclist.locations[i].rotation.v.z);
            }
            printf("  ]\n");
            printf("}\n");
        }
 
        destroyLocationList(&loclist);
    }
 
    /* Finalize */
 
    finalizeConn();
 
    return 0;
}

aostest_mw_client.h - code generated by mw-preprocessor from aostest.c

#ifndef MW_PP_0A0ZNAOU2TTQRV0FVWC5_H
#define MW_PP_0A0ZNAOU2TTQRV0FVWC5_H
 
#include <MW/mw.h>
#include <MW/mw_macros.h>
 
/* This file was generated by mw-preprocessor tool */
 
#include "aostest_types.h"
 
MW_DECL_STRUCT(Vec3f,
  MW_STRUCT_MEMBER(MW_FLOAT, x)
  MW_STRUCT_MEMBER(MW_FLOAT, y)
  MW_STRUCT_MEMBER(MW_FLOAT, z)
)
MW_DECL_STRUCT(Quatf,
  MW_STRUCT_MEMBER(MW_FLOAT, r)
  MW_STRUCT_MEMBER(Vec3f, v)
)
MW_DECL_STRUCT(Location,
  MW_STRUCT_MEMBER(Vec3f, position)
  MW_STRUCT_MEMBER(Quatf, rotation)
)
MW_DECL_PTR(Location_ptr, Location)
MW_DECL_STRUCT_WITH_API(LocationList,
  MW_STRUCT_ARRAY_MEMBER(Location_ptr, locations, MW_INT, num_locations)
  ,
  MW_USER_API(AllocateType, LocationList_Allocate)
  MW_USER_API(DeallocateType, LocationList_Deallocate)
)
MW_DECL_CONST_PTR(const_LocationList_ptr, LocationList)
MW_DECL_FUNC(AOSTest_SetLocations,
  MW_FUNC_ARG(const_LocationList_ptr, locations)
)
MW_DECL_PTR(LocationList_ptr, LocationList)
MW_DECL_FUNC(AOSTest_GetLocations,
  MW_FUNC_RESULT(LocationList_ptr, locations)
)
 
#endif

Server example

aostest_server.c - Middleware GE server implementation of aostest (Array-Of-Structures Test) example

/*
 * This file contains server implementation of aostest (Array-Of-Structures Test) example
 * implemented in C with Middleware GE framework.
 */
 
#include <MW/mw.h>
#include <MW/mw_macros.h>
#include <MW/mw_pp_annotation.h>
 
#include "aostest_types.h"
#include "aostest_mw_server.h"
 
#include <stdio.h>
#include <string.h>
#include "c99fmt.h"
 
/*
 * Declare application structures and service functions of the application.
 * Following macros are processed by mw-preprocessor tool and result
 * is output to the aostest_mw_server.h file.
 */
 
/* mw_declare_struct_with_api(type_name, ...)
 *
 * Declares non-trivial structure type type_name, which require custom behavior via
 * user specified API functions.
 *
 * mw_struct_array_member(ptr_member_name, size_member_name)
 *
 * Declares member in a structure that represents a C-array composed from
 * pointer to the array field and integer array size field.
 *
 * mw_user_api(api_name, user_function_name)
 *
 * Registers user-defined API function user_function_name with a predefined Middleware GE API api_name
 *
 * In our example we need to declare LocationList structure explicitly because it
 * contains C-array composed from locations pointer and num_locations integer and
 * because LocationList requires custom allocation and deallocation functions.
 * Both can't be deduced automatically from the source code.
 */
mw_declare_struct_with_api(LocationList,
    mw_struct_array_member(locations, num_locations),
    mw_user_api(AllocateType, LocationList_Allocate),
    mw_user_api(DeallocateType, LocationList_Deallocate))
 
/* mw_declare_service(service_name, ...)
 *
 * Declares service function type that can be called by the client.
 * All arguments after the function name are argument types and names
 * to the function.
 *
 * Usually types used as arguments do not need to be explicitly declared.
 * Only when types require custom handling an explicit declaration is required.
 */
mw_declare_service(AOSTest_GetLocationsImpl, LocationList * result_value locations)
mw_declare_service(AOSTest_SetLocationsImpl, const LocationList * locations)
 
/** Service implementation */
 
/* In objectLocations list are stored locations sent by the client */
LocationList objectLocations = {0, NULL};
 
/* Receive location list sent by the client and store it to the objectLocations variable */
MW_Result aostest_set_locations_impl(MW_ServiceFuncObj *mw_funcobj, const LocationList *locations)
{
    size_t i;
    size_t num = locations->num_locations;
    for (i = 0; i < num; ++i)
    {
        printf("Location.position %f %f %f\nLocation.rotation %f %f %f %f\n",
                locations->locations[i].position.x,
                locations->locations[i].position.y,
                locations->locations[i].position.z,
                locations->locations[i].rotation.r,
                locations->locations[i].rotation.v.x,
                locations->locations[i].rotation.v.y,
                locations->locations[i].rotation.v.z);
    }
 
    copyLocationList(&objectLocations, locations);
 
    return MW_SUCCESS;
}
 
/* Return location list stored in the objectLocations variable back to the client */
MW_Result aostest_get_locations_impl(MW_ServiceFuncObj *mw_funcobj, LocationList *locations)
{
    copyLocationList(locations, &objectLocations);
 
    return MW_SUCCESS;
}
 
int main(int argc, char **argv)
{
    /*
     * Middleware GE context and connection variables.
     *
     * MW_Context is used for all Middleware GE operations issued from the single thread.
     * Each separate thread require a separate MW_Context instance.
     *
     * MW_Service is a handle to the service which provides implementation
     * of service methods specified in the IDL.
     *
     * MW_Server is a handle to the server which can host multiple services.
     */
 
    MW_Context *ctx;
    MW_Service *service;
    MW_Server *server;
    MW_Result result;
    const char *port = NULL;
    const char *protocol = NULL;
 
    /* Initialize Middleware GE */
    mwInit(&argc, argv);
 
    if (argc > 1)
        port = argv[1];
    else
        port = "8080";
 
    if (argc > 2)
        protocol = argv[2];
    else
        protocol = "jsonrpc";
 
    printf("Server port: %s\n", port);
    printf("Protocol: %s\n", protocol);
 
    /* Create new context */
 
    ctx = mwNewContext();
 
    /* Create a new service */
 
    service = mwNewService(ctx);
 
    /* Add IDL to the service */
 
    result = mwLoadServiceIDLFromString(service,
            "MW",
            "namespace * aostest "
            "struct Vec3f {"
            " float x, "
            " float y, "
            " float z "
            "} "
            "struct Quatf {"
            " float r, "
            " Vec3f v  "
            "} "
            "struct Location {"
            " Vec3f position, "
            " Quatf rotation  "
            "} "
            "struct LocationList { "
            " array<Location> locations "
            "} "
            "service aostest { "
            "  void setLocations(LocationList locations); "
            "  LocationList getLocations(); "
            "} ");
    if (result != MW_SUCCESS)
    {
        fprintf(stderr, "Error: could not parse IDL: %s: %s\n",
                mwGetErrorName(result), mwGetServiceError(service));
        exit(1);
    }
 
    printf("Register aostest.setLocations ...\n");
 
    /*
     * MW_REGISTER_SERVICE_FUNC(service, idl_method_name,
     *                             service_type_name, mapping, service_func_impl)
     *
     * Registers service function implementation with a specified IDL service method.
     *
     * service           - valid MW_Service handle.
     * idl_method_name   - name of the remote service method specified in the IDL.
     * service_type_name - name of the service type declared with the MW_DECL_SERVICE macro.
     * mapping           - mapping of the IDL types to the application types.
     *                     By default 1:1 mapping by names and types is used.
     *                     Note: mapping is not implemented yet.
     * service_func_impl - user function that implements service method.
     */
 
    result = MW_REGISTER_SERVICE_FUNC(service, "aostest.setLocations", AOSTest_SetLocationsImpl, "", aostest_set_locations_impl);
    if (result != MW_SUCCESS)
    {
        fprintf(stderr, "Error: registration failed: %s: %s\n",
                mwGetErrorName(result), mwGetServiceError(service));
        exit(1);
    }
 
    printf("Register aostest.getLocations ...\n");
 
    result = MW_REGISTER_SERVICE_FUNC(service, "aostest.getLocations", AOSTest_GetLocationsImpl, "", aostest_get_locations_impl);
    if (result != MW_SUCCESS)
    {
        fprintf(stderr, "Error: registration failed: %s: %s\n",
                mwGetErrorName(result), mwGetServiceError(service));
        exit(1);
    }
 
    /*
     * Create new server and register service
     */
 
    server = mwNewServer(ctx, "0.0.0.0", port, "/service");
 
    mwAddService(server, "/rpc/aostest", protocol, service);
 
    printf("Starting server...\n");
 
    /* Run server */
 
    result = mwRunServer(server);
    if (result != MW_SUCCESS)
        fprintf(stderr, "Error: could not start server: %s: %s\n",
                mwGetErrorName(result), mwGetServerError(server));
 
    /* Free everything */
 
    mwFreeServer(server);
    mwFreeService(service);
    mwFreeContext(ctx);
    mwFinalize();
 
    /* Free temporary copy of location list */
    destroyLocationList(&objectLocations);
 
    return 0;
}


aostest_mw_server.h - code generated by mw-preprocessor from aostest_server.c

#ifndef MW_PP_U759IC9SH7PSLPQOSZB7_H
#define MW_PP_U759IC9SH7PSLPQOSZB7_H
 
#include <MW/mw.h>
#include <MW/mw_macros.h>
 
/* This file was generated by mw-preprocessor tool */
 
#include "aostest_types.h"
 
MW_DECL_STRUCT(Vec3f,
  MW_STRUCT_MEMBER(MW_FLOAT, x)
  MW_STRUCT_MEMBER(MW_FLOAT, y)
  MW_STRUCT_MEMBER(MW_FLOAT, z)
)
MW_DECL_STRUCT(Quatf,
  MW_STRUCT_MEMBER(MW_FLOAT, r)
  MW_STRUCT_MEMBER(Vec3f, v)
)
MW_DECL_STRUCT(Location,
  MW_STRUCT_MEMBER(Vec3f, position)
  MW_STRUCT_MEMBER(Quatf, rotation)
)
MW_DECL_PTR(Location_ptr, Location)
MW_DECL_STRUCT_WITH_API(LocationList,
  MW_STRUCT_ARRAY_MEMBER(Location_ptr, locations, MW_INT, num_locations)
  ,
  MW_USER_API(AllocateType, LocationList_Allocate)
  MW_USER_API(DeallocateType, LocationList_Deallocate)
)
MW_DECL_PTR(LocationList_ptr, LocationList)
MW_DECL_SERVICE(AOSTest_GetLocationsImpl,
  MW_SERVICE_RESULT(LocationList_ptr, locations)
)
MW_DECL_CONST_PTR(const_LocationList_ptr, LocationList)
MW_DECL_SERVICE(AOSTest_SetLocationsImpl,
  MW_SERVICE_ARG(const_LocationList_ptr, locations)
)
 
#endif

Interface Definition Language

Current Middleware GE IDL is based on Thirft syntax described here: http://thrift.apache.org/docs/idl/. All modifications to the original syntax are documented below. Major extension is the support of annotations based on Web IDL syntax: http://www.w3.org/TR/WebIDL/ and generic types. A number of Thrift features will be removed in a future and replaced by annotations.

Document

[1]  Document        ::=  Header* Definition*

Header

[2]  Header          ::=  Include | CppInclude | Namespace

Middleware GE Include

[3]  Include         ::=  'include' Literal

C++ Include

[4]  CppInclude      ::=  'cpp_include' Literal

Namespace

Thrift uses namespace for mapping to different scripting languages. This should be performed by annotations. We plan to make namespace similar to CORBA's module, so we can annotate all contents of a single IDL file.

[5]  Namespace       ::=  ( 'namespace' ( NamespaceScope Identifier ) |
                                        ( 'smalltalk.category' STIdentifier ) |
                                        ( 'smalltalk.prefix' Identifier ) )

[6]  NamespaceScope  ::=  '*' | 'cpp' | 'java' | 'py' | 'perl' | 'rb' | 'cocoa' | 'csharp'

Definition

Added syntax for defining new annotations.

[7]  Definition      ::=  Const | Typedef | Enum | Senum | Struct | Exception | Service | AnnotationDef

Const

[8]  Const           ::=  'const' FieldType Identifier '=' ConstValue ListSeparator?

Typedef

[9]  Typedef         ::=  'typedef' DefinitionType Identifier

Enum

[10] Enum            ::=  'enum' Identifier '{' (Identifier ('=' IntConstant)? ListSeparator?)* '}'

Senum

[11] Senum           ::=  'senum' Identifier '{' (Literal ListSeparator?)* '}'

Struct

Added syntax for annotating structs.

[12] Struct          ::=  AnnotationList? 'struct' Identifier 'xsd_all'? '{' Field* '}'

Exception

Added syntax for annotating exceptions.

[13] Exception       ::=  AnnotationList? 'exception' Identifier '{' Field* '}'

Service

Added syntax for annotating services.

[14] Service         ::=  AnnotationList? 'service' Identifier ( 'extends' Identifier )? '{' Function* '}'

Field

Added syntax for annotating fields.

[15] Field           ::=  FieldID? AnnotationList? FieldReq? FieldType Identifier ('= ConstValue)? XsdFieldOptions ListSeparator?

Field ID

[16] FieldID         ::=  IntConstant ':'

Field Requiredness

[17] FieldReq        ::=  'required' | 'optional'

XSD Options

TODO This should be removed or replaced by an annotation.

[18] XsdFieldOptions ::=  'xsd_optional'? 'xsd_nillable'? XsdAttrs?

[19] XsdAttrs        ::=  'xsd_attrs' '{' Field* '}'

Functions

oneway attribute was removed from the original Thrift syntax. Use Oneway annotation and void return type for representing functions that never return a value. Support for annotating function and its return type was added.

[20] Function        ::=  AnnotationList? FunctionType AnnotationList? Identifier '(' Field* ')' Throws? ListSeparator?

[21] FunctionType    ::=  FieldType | 'void'

[22] Throws          ::=  'throws' '(' Field* ')'

The first annotation list annotates function as a whole, e.g.:

[Bar(22), Baz] void foobar(i32 i, float f);

The annotation list after function return type annotates only return type, e.g.:

void [Bar(22), Baz] foobar(i32 i, float f);

Each function parameter can be annotated separately, e.g.:

void foobar([Bar(22)] i32 i, [Baz] float f);

Finally, all these cases can be combined together:

[Bar(22), Baz] void [Foo(22), Bar] foobar([Bar(22)] i32 i, [Baz] float f);

Types

Thrift's bool type is renamed to boolean, byte type was renamed to i8. Unsigned integer types u8, u16, u32, and u64 were added. Instead of using predefined list, map and set container types Middleware GE syntax allows to use arbitrary C++/CORBA like generic types: list< i32>, array< i32, 2>, array< i32, array< i32, 4> >.

[23] FieldType       ::=  Identifier | BaseType | GenericType

[24] DefinitionType  ::=  BaseType | GenericType

[25] BaseType        ::=  'boolean' | 'i8' | 'i16' | 'i32' | 'i64' | 'u8' | 'u16' | 'u32' | 'u64' | 'double' | 'string' | 'binary' | 'slist'

[26] GenericType     ::=  Identifier '<' GenericTypeArg (',' GenericTypeArg)* '>'

[27] GenericTypeArg  ::=  Identifier | BaseType | GenericType | IntConstant | DoubleConstant | Literal | ConstList | ConstMap

Constant Values

[28] ConstValue      ::=  IntConstant | DoubleConstant | Literal | Identifier | ConstList | ConstMap

[29] IntConstant     ::=  ('+' | '-')? Digit+

[30] DoubleConstant  ::=  ('+' | '-')? Digit* ('.' Digit+)? ( ('E' | 'e') IntConstant )?

[31] ConstList       ::=  '[' (ConstValue ListSeparator?)* ']'

[32] ConstMap        ::=  '{' (ConstValue ':' ConstValue ListSeparator?)* '}'

Basic Definitions

Literal

[33] Literal         ::=  ('"' [^"]* '"') | ("'" [^']* "'")

Identifier

[34] Identifier      ::=  ( Letter | '_' ) ( Letter | Digit | '.' | '_' )*

[35] STIdentifier    ::=  ( Letter | '_' ) ( Letter | Digit | '.' | '_' | '-' )*

List separator

[36] ListSeparator   ::=  ',' | ';'

Letters and Digits

[37] Letter          ::=  ['A'-'Z'] | ['a'-'z']

[38] Digit           ::=  ['0'-'9']

Annotations

This part is specific to Middleware GE IDL.

[39] AnnotationList  ::= '[' Annotation (',' Annotation)* ']'

[40] Annotation      ::= Identifier AnnotationArgs?

[41] AnnotationArgs  ::= '(' Identifier ('=' ConstValue)? (',' Identifier ('=' ConstValue)? )* ')'

User can define new annotations using following syntax:

[42] AnnotationDef  ::= AnnotationList? 'annotation' Identifier '{' Field* '}'

IDL Examples

namespace * calc
 
exception DivisionByZero {
}
 
// service annotation
[HTTPPort(8080)]
service Calculator {
 
  // function annotation
  [Oneway]
  void ping()
 
  float add(float a, float b)
  float sub(float a, float b)
  float div(float a, float b) throws (DivisionByZero err)
  float mul(float a, float b)
 
  array<i32> addArray(array<i32> a, array<i32> b)
}
Personal tools
Create a book