Skip to content

Interface Description Language (IDL)

Introduction to IDL

The IDL language is a generic language that describes an interface between two processors. The IDL version supported in the SDK is tuned to describe specifically the interface between the application processor and the Hexagon DSPs communicating using FastRPC.

IDL files defining these CPU-DSP interfaces are compiled with the QAIC tool to produce C header files and stub and skeleton source files that are linked into the CPU and DSP modules to enable them to communicate via RPC calls.

This document describes the syntax and semantics of IDL. To learn how to build a project that includes IDL files, please refer to the build instructions. For a better understanding of how the CPU and DSP communicate together, please see the RPC documentation.

Relationship to OMG IDL

The IDL used in the Hexagon SDK is based on the OMG IDL specification but differs in a few respects:

  • All interface methods must have an IDL return a type equivalent to IDL type long. The value returned must be 0 if the method is successful, or an error code on failure. Standard error codes are defined in AEEStdErr.idl which can be found under ${HEXAGON_SDK_ROOT}/incs/stddef.

  • Interfaces may not directly inherit from more than one base interface.

  • The rout parameter modes are used instead of out as explained in the next section.

  • The inrout parameter mode is used instead of inout.

    The parameter mode inrout supports all the basic data types including string and wstring, and structures containing these data types. inrout also supports sequences but not structures containing sequences. dmahandle is also not supported.

  • The dmahandle type is not present in OMG IDL

    This type is used to persistently map buffers on the DSP and bypass cache maintenance for these buffers on the APPS. The user can independently map and unmap the file descriptors on the DSP using the HAP_mem APIs. During the remote call, the file descriptors are mapped on IOMMU and get unmapped when the user unregisters the dmahandle explicitly.

    The advantage of using a dmahandle data type is that users can control the life time of the mapping in DSP. For more information about the dmahandle, please refer to the description of supported IDL types and how they map to C.

    Note: Cache maintenance for this mapping should be handled by the client.

Bounded output parameters

OMG IDL supports three parameter attributes or modes that specify the direction the data flows: in (client to server), out (server to client), and inout (both directions). In OMG IDL, the semantics of out and inout is such that the size of a variable-length parameter cannot be known to or bounded by the client at run time. However, standard practice is for all buffers to be bounded by the client.

The IDL compiler supports output semantics through the IDL keyword rout, which is the bounded analog of out. The "r" in each keyword refers to the UNIX read() system call, where the client provides a buffer and specifies at run time the maximum amount of data to be read into the buffer.

For fixed-size types, there is no difference between the traditional out and the new rout, as the size is statically known and therefore needs not be specified by the client. However, for variable-size types, such as sequences and strings, rout implies an upper bound that is passed as an input parameter from client to server. For example, read() could be defined in IDL as follows:

typedef sequence<octet> SeqOctet;
long read(rout SeqOctet buffer);

In IDL, only a single rout parameter is needed, as it implies the client providing to the server the maximum number of octets to return. See further below for details on how this parameter would be mapped to the remote bindings.

Note that the traditional OMG IDL out and inout parameter modes are not currently supported by the compiler.

NULL Argument Semantics

These are the current rules for when NULL is passed as an argument.

in parameters:

  • Sequence pointers may be NULL when the associated length is 0.

rout parameters:

  • Sequence, string, and wstring pointers may be NULL when the associated length is 0.

Return Values

  • must be equivalent type to long, such as AEEResult from AEEStdDef.idl

  • value 0 indicates success

  • a non-zero code indicates a failure. Any data in rout parameters is not propagated back when a non-zero code is returned.

Wire Format Limitations

in parameters:

  • maximum of 255 in buffers.

The total number of buffers in all input arguments combined cannot exceed 255.

For example

    typedef sequence<octet> buf;
    interface foo
    {
        long bar(in sequence<buf> bufs);
    };

maps to C as

    struct __seq_unsigned_char {
       unsigned char* data;
       int dataLen;
    };
    typedef struct __seq_unsigned_char __seq_unsigned_char;
    typedef __seq_unsigned_char buf;
    int foo_bar(buf* bufs, int bufsLen);

and it will fail for bufsLen > 254.

rout parameters:

  • maximum of 255 rout buffers

in handles:

  • maximum of 15 in dmahandle handles

rout handles:

  • maximum of 15 rout dmahandle handles

Remote Handles

The IDL compiler supports interfaces derived from remote_handle64 which allow the user to maintain a context handle for the opened module. For example, this allows to use a pointer to a local state structure, which holds information between RPC calls.

Using a context handle also allows the client to specify which DSP to use at runtime, and, in the case of a crash on the DSP, to enable a restart of the session.

interface calculator : remote_handle64 {

  // Compute a*b, where a and b are both complex
  long fmult(in float a, in float b, rout float result);
};

This is a special interface that tells the compiler to do the following

  • add a remote_handle64 argument as the first argument of every function in the interface

    For example, with the IDL code above:

    long calculator_fmult(remote_handle64 h, const float a, const float b, float *result);
    
  • add an <interface>_open method

    For example:

    int calculator_open(const char *uri, remote_handle64 *h);
    

    where the uri is a string that allows to specify the domain in which to execute the code.

  • add an <interface>_close method

    For example:

    int calculator_close(remote_handle64 h);
    

With remote handles, users can open multiple instances of the handle as is illustrated in the next section.

In addition, implementors of DSP modules can allocate a local state structure to return as the handle. If there is no local state to maintain, the handle value can be any arbitrary number.

The code example below illustrates both of these approaches:

int calculator_open(const char *uri, remote_handle64 *h) {
   // This value will NOT be returned to the client
   *h = (remote_handle64)0xdeadc0de;
   // but this value will be passed to close when the handle is closed
   // As such, it provides a mechanism to maintain a local structure. For example:
   // *h = (remote_handle64)malloc(100);
   return 0;
}
int calculator_close(remote_handle64 h) {
   // The value returned by open will be presented to the module on close and any other function
   assert(h == (remote_handle64)0xdeadc0de);
   // If instead the remote handle held a valid pointer to a memory allocated in the calculator_open,
   // this is where we could free the memory.
   return 0;
}

Note: The handle value seen by the client on the CPU side is allocated by the framework and not related to the DSP-side handle:

* On the server side (DSP), the value assigned to *h in the `open()` implementation will be presented as the first argument to all subsequent function invocations to this interface and with this handle up until the handle is closed.

* If the server has instance-specific state to maintain, a common approach consists of allocating the instance structure in the `open()` call, and use the address returned as the server-side handle.

* If the server has no instance-specific state to maintain, it is free to return any garbage value that needs not necessarily be unique.

* On the client side (CPU), the FastRPC framework will generate and return to the caller of open() a unique handle that is not derived from the server-side handle provided by the server open() implementation: the FastRPC retains a mapping internally from client-side to server-side handles, so that an invocation made on a client-side handle will be received on the server with the corresponding server-side handle, but neither side ever sees the other side's handles.

Domains

The term domain is used to refer to any of the DSPs that support FastRPC on a given target.

Interfaces deriving from remote_handle64 will provide the user with explicit control over the module lifetime. A DSP-side process will be open as long as there is at least one handle reference to it.

remote_handle64 h1 = -1, h2 = -1;
float c1,c2,result;
//open h1, if h1 is the first handle for the default domain it will instantiate a new DSP process
assert(0 == calculator_open(calculator_URI, &h1));

//open h2, the process is still open
assert(0 == calculator_open(calculator_URI, &h2));

//close h1, since we also opened h2, the process is still open
assert(0 == calculator_close(h1));

//call using h1 will fail
assert(0 != calculator_fmult(h1, &c1, &c2, &result));

//call using h2 will pass
assert(0 == calculator_fmult(h1, &c1, &c2, &result));

//close h2, this is the last handle to the process
//it will shutdown the process on the DSP side and free all its resources
(void)calculator_close(h2);

Domain restart

Since users have explicit control over the handle lifetimes they can detect errors and re-open the handle.

assert(0 == calculator_open(calculator_URI, &h1));
int nErr=calculator_fmult(h1, &c1, &c2, &result);
if (nErr == AEE_ENOSUCH || nErr == AEE_EBADSTATE) {
   /* AEE_ENOSUCH is returned when Protection Domain Restart (PDR) happens and
    * AEE_EBADSTATE is returned when PD is exiting or crashing.*/
   (void)calculator_close(h1);
   h1 = -1;
   // restart the DSP process
   assert(0 == calculator_open(calculator_URI, &h1));

   //try fmult again
   assert(0 == calculator_fmult(h1, &c1, &c2, &result));
}

For a full example illustrating that approach, please refer to the calculator example.

Typically once a DSP-side process is killed or crashes, all methods should return AEE_ENOSUCH as defined in ${HEXAGON_SDK_ROOT}/incs/stddef/AEEStdErr.h.

Domain Routing

Users can explicitly specify the domain on which they need to execute their code. A session is opened on a particular domain by appending a static string representing that domain and defined in $HEXAGON_SDK_ROOT\incs\remote.h to the auto-generated interface URI:

#include "remote.h"
...
remote_handle cdsp, adsp;
const char *uri_cdsp = calculator_URI CDSP_DOMAIN;
const char *uri_adsp = calculator_URI ADSP_DOMAIN;
assert(!calculator_open(uri_cdsp, &cdsp));
assert(!calculator_open(uri_adsp, &adsp));
assert(!calculator_sum(cdsp, buf, bufLen, &val));
assert(!calculator_sum(adsp, buf, bufLen, &val));
assert(!calculator_close(cdsp));  // kills the remote PD on aDSP if this is the last handle using the cDSP
assert(!calculator_close(adsp));  // kills the remote PD on aDSP if this is the last handle using the aDSP

Async FastRPC support

Please refer to the Asynchronous FastRPC documentation for more details on how to use an asynchronous FastRPC function.

  • To declare an asynchronous FastRPC function in IDL, use the syntax below:

    interface lib : remote_handle64 {
      // synchronous calls
      long foo(in sequence<octet> inPtr, rout sequence<unsigned long> outPtr, in float alpha);
    
      // declare an asynchronous-only RPC
      async long foo_async(in sequence<octet> inPtr, rout sequence<unsigned long> outPtr, in float alpha);
    }
    

    Corresponding method declaration in header file would have fastrpc_async_descriptor_t as a parameter:

    1. Multi-domain version of IDL generated method declarations for sync and async

      int lib_foo(remote_handle64 h, const unsigned char* inPtr, int inPtrLen, unsigned int* outPtr, int outPtrLen, float alpha);
      int lib_foo_async(remote_handle64 h, fastrpc_async_descriptor_t* asyncDesc, const unsigned char* inPtr, int inPtrLen, unsigned int* outPtr, int outPtrLen, float alpha);
      
    2. Single-domain version of IDL generated method declarations for sync and async

      int lib_foo(const unsigned char* inPtr, int inPtrLen, unsigned int* outPtr, int outPtrLen, float alpha);
      int lib_foo_async(fastrpc_async_descriptor_t* asyncDesc, const unsigned char* inPtr, int inPtrLen, unsigned int* outPtr, int outPtrLen, float alpha);
      

    Note: An async method can be forced to execute synchronously by passing a NULL async descriptor.

  • Async stub methods are invoked like synchronous methods but include an additional descriptor as the first argument. Async calls will return 0 on successful submission of the job to DSP. A non-zero return value indicates the failure to make the async RPC call.

    nErr = lib_foo_async(handle, &desc, inbuf, inbufLen, outbuf, outbufLen, alpha);
    
  • The skeleton remains identical to the conventional (synchronous) FastRPC skeleton, with the exception that the second argument which follows the remote handle, is the descriptor.

  • Only sequences can be output arguments (inrout or rout) when declaring an asynchronous function. All other argument types declared as inrout or rout will cause a compilation error:

    1. Primitive type
    2. Array
    3. Dmahandle
    4. Wide string
    5. String
    6. Enum
    7. Struct

    Note: If an argument of any of these types needs to be specified as an output, it can be declared as a sequence with one element.

  • The sequences created for async methods must be allocated and registered with FastRPC as shared ION buffers. Async call with non-ION buffers for sequences will return AEE_EBADPARM error at runtime.

  • RPC functions that need to be available synchronously and asynchronously must be declared twice in the IDL file, once with and once without the async keyword

    Note: Clients can also call asynchronous RPC functions synchronously by using a NULL descriptor.

Stub Skel mismatch check feature

QAIC provides a new feature to ensure that the CPU and DSP use a compatible code version when making FastRPC calls.

Supported targets for this feature: Kailua, and later targets. On older targets, the use of version in the IDL files will either be ignored or cause FastRPC to return AEE_ERPC instead of AEE_ESTUBSKELVERMISMATCH. It is therefore not recommended to use this feature on older targets to avoid any confusion.

Behaviour of the feature on Supported targets

Stub Version Skel Version Result
IDL_VERSION not enabled IDL_VERSION not enabled No error
IDL_VERSION not enabled IDL_VERSION enabled AEE_ESTUBSKELVERMISMATCH error
IDL_VERSION enabled IDL_VERSION not enabled AEE_ESTUBSKELVERMISMATCH error
IDL_VERSION enabled IDL_VERSION enabled if (Stub_Version <= Skel_Version)
then No error

if (Skel_Version == "")
then AEE_ESTUBSKELVERMISMATCH error

if (Stub_Version > Skel_Version)
then AEE_ESTUBSKELVERMISMATCH error

Behaviour of the feature on Non-supported targets

Stub Version Skel Version Result
IDL_VERSION not enabled IDL_VERSION not enabled No error
IDL_VERSION not enabled IDL_VERSION enabled AEE_ERPC error
IDL_VERSION enabled IDL_VERSION not enabled No error
IDL_VERSION enabled IDL_VERSION enabled if(Stub_Version <= Skel_Version) then No error

if(Skel_Version == "") then No error

if(Stub_Version > Skel_Version) then AEE_ERPC error

To enable this feature, users must insert the line below in their IDL file:

const string IDL_VERSION = "<Version_number>";

For example:

#include "AEEStdDef.idl"
#include "remote.idl"

const string IDL_VERSION = "1.2.3";

interface calculator : remote_handle64 {
    long sum(in sequence<long> vec, rout long long res);
    long max(in sequence<long> vec, rout long res);
};

Note: The name of this variable cannot be anything other than "IDL_VERSION".

The user must ensure that the IDL_VERSION for the skel file must be >= the IDL_VERSION used to generate stub file. Otherwise, the AEE_ESTUBSKELVERMISMATCH error code will be returned at run time when attempting to make a FastRPC call.

Version Number Format

The valid version format is:

major_number[.minor_number[.patch_number]]

major_number, minor_number, and patch_number should be non-negative integers only and must be <= 999. If the version format is not matching, QAIC will throw a compilation error:

IDLVERSION format violated. Use "xxx.xxx.xxx" format where x is a digit

Example: 123, 123.12, and 123.12.3 are valid version formats.

Sample IDL

A sample IDL file is shown below to illustrate the use of common IDL constructs.

#include "AEEStdDef.idl"  // Needed for 'AEEResult'

interface math_example
{
  // This structure is specific to this interface, so we scope it within the
  // interface to avoid pollution of the global namespace.
  struct Complex
  {
    float real; // Real part
    float imag; // Imaginary part
  };

  // A vector, consisting of 0 or more Numbers.
  typedef sequence<Complex> Vector;

  // Compute a*b, where a and b are both complex
  AEEResult Mult(in Complex a, in Complex b, rout Complex result);

};

This IDL interface will result in the generation of the following C interface:

typedef struct math_example_Complex math_example_Complex;
struct math_example_Complex {
   float real;
   float imag;
};

typedef struct _math_example_Vector__seq_math_example_Complex _math_example_Vector__seq_math_example_Complex;
typedef _math_example_Vector__seq_math_example_Complex math_example_Vector;

struct _math_example_Vector__seq_math_example_Complex {
   math_example_Complex* data;
   int dataLen;
};

__QAIC_HEADER_EXPORT AEEResult __QAIC_HEADER(math_example_Mult)(const math_example_Complex* a, const math_example_Complex* b, math_example_Complex* result) __QAIC_HEADER_ATTRIBUTE;

Note: IDL sequences turn into pointers followed by a length value. The length of a sequence is defined as the number of elements and not the number of bytes in the array.

Include directives and code generation

Many IDL files include other IDL files in order to make use of types and interfaces declared externally. For example, when defining a Component Services interface in IDL, AEEIQI.idl needs to be included for the definition of IQI, from which all CS interfaces must be derived. However, one important difference between #include in IDL and #include in C/C++ is that in IDL, code is not generated for modules, interfaces, and types included from other IDL files. For example, consider the following IDL:

interface foo { /* definition of foo here */ };
interface bar : foo { /* definition of bar here */ };

If this IDL is compiled, the output will contain the appropriate code for both foo and bar. However, suppose the foo definition is moved to foo.idl, and the IDL being compiled is changed as follows:

#include "foo.idl"
interface bar : foo { /* definition of bar here */ };

In this case, only code for bar will be generated. Although the contents of foo.idl are read by the compiler, no code is generated for foo because it is defined in an external (included) IDL file. Instead of generating code for foo, the compiler will translate the #include in the IDL to a #include in the output, with the extension changed from ".idl" to ".h".

There can be two approaches to include an IDL file in another IDL file:

  • Create an interface in the parent IDL (foo) and inherit that interface in the child IDL (bar).

    foo.idl

    interface foo{
      long function1();
      long function2(in long x);
    };
    

    bar.idl

    // foo.idl included outside bar interface
    #include "foo.idl"
    
    interface bar: foo{
        long baz();
    };
    

    Here bar.h will contain bar_function1 and bar_function2, along with bar_baz

    This approach gives the flexibility to bundle up some functions together to be inherited for a particular interface.

    struct, typedef, etc., can be declared outside foo interface but functions must be declared inside foo interface.

    Multiple interfaces can be declared in foo.idl, but only one interface can be inherited in bar.idl

    foo.idl

      interface foo{
          long baz1();
      };
    
      interface foo1{
          long baz2();
      };
    

    bar.idl

    Either this is allowed

      interface bar : foo {
          long baz();
      };
    

    OR this is allowed

      interface bar : foo1 {
          long baz();
      };
    

    Note: An IDL having multiple interface declarations (here foo.idl) cannot be used for shared obejct creation, it can only work as parent IDL.

  • Declaring in foo.idl without any interface and including "foo.idl" inside the interface of bar.idl

    foo.idl

    #include "AEEStdDef.idl"
    AEEResult func();
    

    bar.idl

    interface bar{
      // foo.idl included inside bar interface
      #include "foo.idl"
      long baz();
    };
    

    Here bar.h will contain bar_func along with bar_baz In this approach interface bar will have access to everything declared in foo.idl.

    Note: You cannot define an interface in foo.idl in this case.

In addition, when an IDL file includes #include "remote.idl" and its interface inherits remote_handle64, the auto-generated header file will contain an interface_open and interface_close function declaration, and all other functions will have a remote_handle64 h as their first argument, as described here

Header file generation

The header files generated by QAIC are made to resemble hand-written C headers. For each interface name in IDL, for each function name in IDL; functions are generated in header file in the following format:

int `interface`_`function`(arg1, arg2, ... argN);

Mapping to C

This section details the mapping of IDL constructs to C types.

Basic built-in types

The following table lists the mapping of IDL basic types to C.

IDL Type C Type
wchar _wchar_t
octet unsigned char
char char
short short
unsigned short unsigned short
long int
unsigned long unsigned int
long long int64
unsigned long long uint64
int8 int8
uint8 uint8
int16 int16
uint16 uint16
int32 int32
uint32 uint32
int64 int64
uint64 uint64
int8_t int8_t
uint8_t uint8_t
int16_t int16_t
uint16_t uint16_t
int32_t int32_t
uint32_t uint32_t
int64_t int64_t
uint64_t uint64_t
float float
double double
boolean boolean
dmahandle int (handle), uint32 (offset), uint32 (length)

Definitions for _wchar_t, uint64 and int64 can be found in AEEStdDef.h

The dmahandle type takes in three parameters: handle to the buffer, offset into the buffer and size of the buffer allowing to mapping, coherency, and cache operations.

Constants

Constant declarations in IDL are mapped to #defines in C, with expressions evaluated.

  • Constant declaration in IDL:

    const short MAX_TRIES = 5 + 10 - 4;
    
  • Corresponding C macro:

    #define MAX_TRIES 11
    

Identifiers

Declaring constant in IDL results in declaring a macro in C and C++.

For example, the following IDL constant declaration:

const short MY_CONSTANT = 3;

will result in the following C/C++ code:

#define MY_CONSTANT 3

It is recommended that C and C++ keywords not be used as identifiers in IDL. However, if a keyword is used as an identifier, it will be prefixed with _cxx_ in the generated output.

For example, the following constant declaration in IDL:

const short break = 3;

will result in the following C/C++ code:

#define _cxx_break 3

Interfaces

Types and functions declared within an interface must be scoped within that interface, any such types are prepended with the name of the enclosing interface and an underscore.

Any type defined within an interface will be extracted and defined before the corresponding structure in the mapping.

  • IDL declaration interface IFoo { struct inner { / ... / };

      long process(in short a);
    };
    
  • Corresponding C prototype

    typedef struct IFoo_inner
    {
      /* ... */
    } IFoo_inner;
    
    int IFoo_process(short int a);
    

Methods

Each method of an interface is mapped as a function.

in parameter

in parameters are passed by value. These input arguments are mapped as const. All user-defined types (struct, union) are passed as pointers to the defined type. Note that no in pointer may be NULL.

An in parameter example is shown below.

  • IDL declaration

    struct point
    {
      short x;
      float y;
    };
    
    interface ITest
    {
    };
    
    interface IFoo
    {
    
      long process(in short id,
      in string name,
      in point origin);
    };
    
  • Corresponding C prototype

    typedef struct point
    {
      short int x;
      float y;
    } point;
    
    int IFoo_process(short int id,
      const char* name,
      const point* origin);
    

rout parameter

rout parameters are passed by reference as a pointer.

  • IDL declaration

    interface IFoo
    {
       long process(rout short id,
         rout string name,
         rout point origin);
    };
    
  • Corresponding C prototype

    int IFoo_process(short int* id,
        char* name,
        int nameLen,
        point* origin);
    

inrout parameter

inrout parameters are passed by reference as a pointer.

This is very similar to rout parameter and an example of an inrout is shown below.

  • IDL declaration

    interface IFoo
    {
       long process(inrout short id,
          inrout string name,
          inrout point origin);
    };
    
  • Corresponding C prototype

    int IFoo_process(short int* id,
          char* name,
          int nameLen,
          point* origin);
    

Structures

IDL structures are mapped to C structures, with a typedef to allow the name of the structure to be used as a type. Note that types declared within a structure will have the name of the enclosing structure prepended to their names, as is done with definitions within interfaces.

  • IDL declaration

    struct extended_point
    {
      short x;
      float y;
    };
    
  • Corresponding C prototype

    typedef struct extended_point
    {
      short int x;
      float y;
    } extended_point;
    

Enumerations

IDL enumerated types are mapped to C enumerated types, with a typedef to allow the name of the enum to be used as a type. A placeholder enumerator is added to each enum to ensure binary compatibility across compilers.

  • IDL declaration

    enum color
    {
      RED,
      ORANGE,
      YELLOW,
      GREEN,
      BLUE
    };
    
  • Corresponding C prototype

    typedef enum color
    {
      RED,
      ORANGE,
      YELLOW,
      GREEN,
      BLUE,
      _32BIT_PLACEHOLDER_color = 0x7fffffff
    } color;
    

The starting value for an enum is always 0.

Unions

Unions are not supported at this time.

Arrays

IDL fixed-size arrays are mapped to C arrays.

  • IDL declaration

    struct foo
    {
      long sum[2];
    };
    
  • Corresponding C prototype

    typedef struct foo
    {
      int sum[2];
    } foo;
    

Sequences

Sequences allow to represent arrays where the length is specified at runtime.

For each sequence type sequence<T>, a corresponding structure __seq_T is generated with two members:

T* data;
int dataLen;

The dataLen member specifies the number of elements in the array data (and not the number of bytes). Note that sequence lengths are always in terms of the number of elements in the sequence, not the number of bytes required to store the sequence.

Consider the following mapping example for a sequence of long integers.

  • IDL declaration

    typedef sequence<long> seqlong;
    
  • Corresponding C prototype

    struct __seq_int
    {
      int* data;
      int dataLen;
    };
    
    typedef __seq_int seqlong;
    

This structure is used when constructing sequences of sequence types.

Note: Memory buffers exchanged between the CPU and DSP should be declared as ION memory buffers using the RPC MEM APIs.

in parameter of a method

When a sequence<T> is specified as an in parameter of a method of an interface, the mapping generates two arguments.

The first argument is a constant array pointer. It must be valid unless its length is 0, in which case the pointer may be NULL.

The second argument specifies the total number of elements of the array and not the array size in bytes.

  • IDL declaration

    typedef sequence<long> seqlong;
    
    interface IFoo
    {
      long process(in seqlong sums);
    };
    
  • Corresponding C prototype

    int IFoo_process(
        const int* sums,
        int sumsLen);
    

rout parameter of a method

An rout sequence is similar to an in sequence with the exception that the array pointer is not declared as a constant:

  • IDL declaration

    typedef sequence<long> seqlong;
    
    interface IFoo
    {
      long process(rout seqlong sums);
    };
    
  • Corresponding C prototype

    int IFoo_process(
         int* sums,
         int sumsLen);
    

inrout parameter of a method

An inrout sequence is declared in C in the same way an rout sequence.

  • IDL declaration

    typedef sequence<long> seqlong;
    
    interface IFoo
    {
      long process(inrout seqlong sums);
    };
    
  • Corresponding C prototype

    // see seqlong above
    int IFoo_process(
         int* sums,
         int sumsLen);
    

Member of a structure

A sequence may also be declared as a member of a structure:

  • IDL declaration

    typedef sequence<long> seqlong;
    
    struct Atm
    {
      seqlong sums;
    };
    
  • Corresponding C prototype

    typedef struct Atm Atm;
    struct Atm
    {
      int* sums;
      int sumsLen;
    };
    

Within another sequence, or an array

Sequences may also be used within another sequence or as part of an array:

  • IDL declaration

    typedef sequence<long> seqlong;
    
    typedef sequence<seqlong> long2d;
    
    struct s
    {
      seqlong five_sequences[5];
    };
    
  • Corresponding C prototype

    struct __seq_int
    {
      int* data;
      int dataLen;
    };
    
    typedef __seq_int seqlong;
    
    struct __seq_seqlong
    {
      seqlong* data;
      int dataLen;
    };
    
    typedef __seq_seqlong long2d;
    
    typedef struct s s;
    
    struct s
    {
      seqlong five_sequences[5];
    };
    

Strings

The IDL string type is mapped as char*, and wstring as _wchar_t* (where _wchar_t is typedef-ed to unsigned short). When used anywhere other than an in parameter, the pointer is accompanied by a size, which allows the client to specify the number of characters (char for string, _wchar_t for wstring) allocated for the string or wstring. This is the length of the buffer in characters, not the length of the string -- since strings are null- terminated in C, the length of the string is computable. All length associated with a string or wstring include the null-terminator.

Note: In this section, characters should be interpreted as meaning one-byte chars for string types, and a two-byte _wchar_ts for wstring types. The term "character" is not used here in the lexical sense -- when storing text, character set and encoding considerations are left to the application, and it is therefore possible for a lexical character to require more than one IDL character (non-zero byte) to represent it.

in parameter of a method

string is mapped as const char* and wstring as const _wchar_t*.

  • IDL declaration

    interface IFoo
    {
      long process(in string name);
      long process_w(in wstring name);
    };
    
  • Corresponding C prototype

    int IFoo_process(const char* name);
    int IFoo_process_w(const _wchar_t* name);
    

rout parameter of a method

The client must provide a valid buffer, dcl, which can hold up to dclLen characters (including the null terminator). However, when dclLen is 0, dcl may be NULL. On successful return, the returned string dcl will always be null terminated at the dclLen - 1 character.

An example of an rout string is shown below.

  • IDL declaration

    interface IFoo
    {
      long process(rout string name);
    };
    
  • Corresponding C prototype

    int IFoo_process(char* name,
      int nameLen);
    

inrout parameter of a method

This is very similar to rout parameter and an example of an inrout string is shown below.

  • IDL declaration

    interface IFoo
    {
      long process(inrout string name);
    };
    
  • Corresponding C prototype

    int IFoo_process(char* name,
      int nameLen);
    

Note: For both types, the length parameters refer to the length of the buffer in characters (one-byte chars for strings, and two-byte _wchar_ts for wstrings), not the length of the string. The lengths are inclusive of a null terminator.

Member of a structure

Within a structure, a string is mapped as though it were a sequence<char>, and a wstring as though it were a sequence<wchar>. However, as with strings and wstrings, the buffers are always required to be null terminated. The mapping for sequences within structures is detailed in Sequence, part of which is duplicated here for clarity.

  • IDL declaration

    struct Atm
    {
      string ssn;
    };
    
  • Corresponding C prototype

    typedef struct Atm Atm;
    struct Atm
    {
      char* ssn;
      int ssnLen;
    };
    

The second field ssnLen specifies the total size of the buffer ssn, in characters.

Within a sequence

When a string or wstring is used within a union or a sequence, it is mapped as a _cstring_t or _wstring_t. Both of these types are structures containing a pointer to a buffer and a buffer length. This structure is the same as the structure that would be generated for a sequence<char> in the case of string, or sequence<wchar> in the case of wstring. See Sequence for details on the structure generated for each sequence.

The semantics of the dataLen field are the same as those for a string when it used as the member of a structure; see Member of a structure for details.

  • IDL declaration

    typedef sequence<string> seqstring;
    
  • Corresponding C prototype

    // Note: this struct is only defined
    // once, at the top of each file
    struct _cstring_t
    {
      char* data;
      int dataLen;
    };
    
    struct __seq_string
    {
      _cstring_t* data;
      int dataLen;
    };
    
    typedef __seq_string seqstring;
    

NULL and empty strings

Strings in IDL interfaces are never NULL pointers. Strings in IDL are never absent or omitted by being NULL because they can't be. They either have a value or they are the empty string.

An empty string is a valid pointer to a buffer with a single byte of value 0. ("" is an empty string)

QAIC

qaic, Qaic's Another Idl Compiler, is a command-line executable used to implement remote shared objects for the DSP Platform. A shared object is called remote if its methods can be invoked from outside the domain it resides in. The user of a remote object does not need to know where the object is hosted, or what language the object is implemented in. Instead, the user calls methods on a stub object generated by qaic. The stub marshals the input into a shared wire format, and ships the data off to the domain where the object is hosted. The host domain implements a skel object, also generated by qaic, that unmarshals the data, and invokes the requested method on the native object. The FastRPC framework description provides more information on the framework enabling this remote communication on Qualcomm devices.

To generate stubs and skels, qaic requires the interface to an object be strictly defined. The syntax for defining an object interface is called IDL and covered in the previous sections. qaic compiles IDL files into headers, stubs, and skels. The generated header can be used to implement the native object, and for users to call methods on the object. The stub and skel are compiled into a shared object.

qaic is invoked automatically when building a project. This section discusses how to run and use the compiler separately, including details of the various command-line options.

Command-line usage

The basic command-line syntax of the tool is:

qaic [options] file1.idl [file2.idl ... fileN.idl]

Each file specified on the command-line will be compiled by the tool according to the options specified. The available options are:

  • -mdll or --map-dll

    Generate DLL mapping

  • -o=PATH

    Use path as the output path. All generated files will be output to the specified path. The default is the current directory ('.').

  • --cpp or -p=CPP

    Use CPP as the C preprocessor. The value CPP must name an executable program, and cannot contain any arguments. To pass arguments to the preprocessor, use --arg-cpp (-pa).

  • --arg-cpp=ARG or -pa=ARG

    Pass additional argument arg to the preprocessor. To specify arguments that are themselves options, use the form -pa=ARG (for example, -pa=-E). Specifying -pa -E will cause the -E to be interpreted as an option to qaic instead of to the preprocessor. Note that for Comment pass-through to work properly, the preprocessor must be set to not strip comments from the source. Typically the flag to do this is -C, making the appropriate argument to qaic -pa=-C.

  • --include-path=PATH or -I=PATH

    Include path in the search path for included files. May be used multiple times.

  • --indent=WIDTH or -i=WIDTH

    Use an indentation width of width spaces in the generated code.

  • --warn-undefined or -Wu

    Issue warning for forward-declared interfaces that are never defined.

  • --define=SYMBOL or -D=SYMBOL

    Predefine macro for the preprocessor.

  • --header-only or -ho

    Only generate a header. Stub and skeleton code is not generated if this option is specified.

  • --remoting-only or -ro

    Only generate stub and skeleton code. The corresponding header is not generated if this option is specified.

  • --parse-only or -s

    Parse the IDL and perform semantic checking, but do not generate any output. Note that IDL files accepted without errors by the compiler with -s are not guaranteed to work without errors when code generation is enabled.

  • -v

    Print the version of the compiler.

  • -h

    Print a brief help message.

Usage examples

The examples below illustrate typical usage of the IDL compiler.

qaic --header-only foo.idl bar.idl

The above command compiles foo.idl to the remote header file foo.h, and bar.idl to the remote header file bar.h. No remoting code is generated.

qaic foo.idl

The above command compiles foo.idl to a remote header file foo.h, along with the following remoting code:

File Name Description
foo_stub.c C stub implementation
foo_skel.c C skeleton implementation
foo.h Common header for stub and skel

qaic -I../bar -I../far -o out foo.idl

The above command compiles foo.idl. It uses ../bar and ../far as the search path for any include files. It uses out as the result directory, and generates out/foo.h, out/foo_stub.c and out/foo_skel.c files.

Using other preprocessors

By default, qaic uses an internal preprocessor. It may be desirable to use a different preprocessor instead. The Microsoft C preprocessor can be used by having the compiler invoke cl /E /C, which is done with the following command-line. Note that for this to work, cl must be in the PATH.

qaic -p=cl -pa=/E -pa=/C file1.idl [file2.idl ... fileN.idl]

The ARM C/C++ compiler can also be used to preprocess IDL. Provided armcc is in the PATH, this can be done with the following command-line.

qaic -p=armcc -pa=-E -pa=-C file1.idl [file2.idl ... fileN.idl]

Note that -pa=-E must be used instead of -pa -E, since in the latter case the -E is interpreted by qaic as being an option to qaic, not to the preprocessor.

Error messages

Any output printed by the compiler is due to either an error or a warning. Warnings include the text warning: at the beginning of the message, and do not abort code generation. Any message not preceded by warning: is an error, which causes compilation to abort. Both errors and warnings include a reference to the file, line, and position within that line (starting at 0) where the error or warning occurred. Additional details on select errors are given in the following subsections.

Identifier 'abc' clashes with an introduced type

The OMG IDL specification includes complex scoping rules based not only on where types are defined, but also on where they are used. Specifically, the first component (identifier) of a qualified type name is introduced into the scope where it is used, preventing the use of any identifier with the same name in that scope. Fully-qualified names, which start with ::, are considered to have an empty first component, and thus result in no type introduction.

Consider the following example, which illustrates the basic type introduction rules. For full details, see the "Names and Scoping" section of OMG IDL Syntax & Semantics.

struct Name
{
    string first, last;
};

struct Address
{
   string street, city, state, country;
};

module M
{
   typedef long Age;
};

struct Person
{
   Name   name;    // invalid IDL: 'name' clashes with 'Name'
   string address; // OK: 'Address' not introduced in this scope
   M::Age age;     // OK: only 'M', not 'Age', is introduced
};

In the Person structure above, the use of the Name type introduces it into the scope of Person, which prevents the member from being called name. The second member, address, is fine because the Address type is not defined within the scope of Person and has not been introduced. The reference to M::Age only causes the first component, M, to be introduced into the scope of Person, thus the age member is also without error.

Clashes with introduced types can generally be resolved by changing the qualification to avoid the type introduction. For instance, if in the above example the type of the name member of the Person structure were written ::Name, no type introduction would occur, which would avoid the name clash.

Comment pass-through

When qaic identifies code comments as a Doxygen comments, or ordinary comments. Doxygen comments are passed through to generated header files. When documenting interface methods, Doxygen comments are generally preferred.

Doxygen comments

When a method is documented with the Doxygen syntax, qaic will attempt to translate the documentation to the target language in any output files.