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 ofout
as explained in the next section. -
The
inrout
parameter mode is used instead ofinout
.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 interfaceFor example, with the IDL code above:
long calculator_fmult(remote_handle64 h, const float a, const float b, float *result);
-
add an
<interface>_open
methodFor 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
methodFor 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:
-
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);
-
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
orrout
) when declaring an asynchronous function. All other argument types declared asinrout
orrout
will cause a compilation error:- Primitive type
- Array
- Dmahandle
- Wide string
- String
- Enum
- 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
keywordNote: 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 errorif (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 containbar_function1
andbar_function2
, along withbar_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 outsidefoo
interface but functions must be declared insidefoo
interface.Multiple interfaces can be declared in
foo.idl
, but only one interface can be inherited inbar.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 ofbar.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 containbar_func
along withbar_baz
In this approach interface bar will have access to everything declared infoo.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 toqaic
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 toqaic -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.