qaic, Qaic's Another Idl Compiler, is a command-line executable used to implement remote shared objects for the DSP Platform. A remote shared object is a software component whose functions are implemented on the DSP but can be invoked directly from an application running on the Application Processor (APPS). The application on APPS calls these functions as if they were local, without requiring knowledge of the function’s execution location or the implementation language used on the DSP. To enable this communication, the user interacts with a stub object generated by qaic. The stub marshals the input parameters into a shared wire format and transmits the data to the domain where the remote object is hosted - typically the DSP. On the DSP, the host environment integrates a skel object, also generated by qaic, that unmarshals the data, and invokes the requested method on the native object. This remote communication between APPS and DSP is facilitated by the FastRPC framework, which provides the underlying infrastructure for cross-domain function invocation 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. 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.
The inc folder contains standard IDL files that can be included in your IDL definitions:
This file defines the remote_handle64 interface for managing remote execution sessions on DSP domains. It provides:
open() - Opens a handle in the specified domain (aDSP, mDSP, or DEFAULT). Creates a session on first handle, including device initialization, PD creation on the DSP, and loading the shared object with the skeleton function.
<interface>_URI&_dom=<domain> or file:///<sofilename>?<interface>_skel_handle_invoke&_modver=1.0close() - Closes a handle. When the last handle is closed, the session is terminated and all resources are released.
This interface is fundamental for establishing and managing FastRPC communication sessions between the application processor and DSP domains.
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:
--map-dll or -mdll
Generate DLL mapping
--output-path=PATH or -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.
--standard-types or -st
Use standard C types (bool, uint16_t, int32_t, etc.) instead of legacy types (boolean, uint16, int32, etc.) in generated code. When this flag is used, the generated header files will include <stdint.h> and <stdbool.h> (if boolean types are used). See the "Basic built-in types" section for detailed type mappings.
--version or -v
Print the version of the compiler.
--help or -h
Print a brief help message.
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 stub and skel code is generated.
qaic foo.idl
The above command compiles foo.idl to a remote header file foo.h, along with the following stub and skel 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.
qaic -st foo.idl
The above command compiles foo.idl using standard C types. The generated foo.h will use types like bool, int32_t, uint32_t, int64_t, and uint64_t instead of legacy types like boolean, int32, uint32, int64, and uint64. The header will automatically include <stdint.h> and <stdbool.h> (if boolean types are used).
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. Ensure that armcc is available in the system's PATH. The preprocessor can be invoked using the following command:
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.
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.
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.
qaic identifies code comments as either Doxygen comments or ordinary comments. Doxygen comments are preserved in the generated header files and are generally preferred for documenting interface methods.
When a method is documented with the Doxygen syntax, qaic will attempt to translate the documentation to the target language in any output files.
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, stub and skeleton source files that are linked into the CPU and DSP modules to enable them to communicate via RPC calls.
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.
Interfaces may not directly inherit from more than one base interface.
The rout parameter modes are used instead of out.
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.
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 functional difference between the traditional out and the newer rout parameter attribute, as the size is statically known and does not need to 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.
Note that the traditional OMG IDL out and inout parameter modes are not currently supported by the compiler.
These are the current rules for when NULL is passed as an argument.
in parameters:
NULL when the associated length is 0.rout parameters:
NULL when the associated length is 0.must be equivalent type to long
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.
in parameters:
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:
rout buffersin handles:
in dmahandle handlesrout handles:
rout dmahandle handlesA sample IDL file is shown below to illustrate the use of common IDL constructs.
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
long 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 int __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.
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
long 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.
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);
This section details the mapping of IDL constructs to C types.
The following table lists the mapping of IDL basic types to C.
Default mapping (without -st flag):
| IDL Type | C Type |
|---|---|
| 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 |
| 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) |
With -st or --standard-types flag:
When the -st flag is used, QAIC generates code using standard C types instead of legacy types. The following types are affected:
| IDL Type | C Type (with -st) |
|---|---|
| int8 | signed char |
| uint8 | unsigned char |
| int16 | signed short |
| uint16 | unsigned short |
| int32 | int32_t |
| uint32 | uint32_t |
| long long | int64_t |
| unsigned long long | uint64_t |
| int64_t | int64_t |
| uint64_t | uint64_t |
| boolean | bool |
| dmahandle | int (handle), uint32_t (offset), uint32_t (length) |
When using -st, the generated header files will include:
#include <stdint.h> for standard integer types#include <stdbool.h> for boolean type (if boolean is used in the IDL)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.
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
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
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);
Each method of an interface is mapped as a function.
in parameterin 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 parameterrout 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 parameterinrout 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);
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;
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 are not supported at this time.
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 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.
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);
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);
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);
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;
};
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];
};
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.
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);
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);
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.
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.
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;
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)
This section guides you through building the QAIC compiler from source using GHC 9.10.3 and Cabal 3.12.1.0.
Step 1: Install the Haskell toolchain
ghcup install ghc 9.10.3
ghcup set ghc 9.10.3
ghcup install cabal 3.12.1.0
ghcup set cabal 3.12.1.0
Step 2: Install Cygwin
Step 3: Build QAIC
Open the Cygwin terminal and run:
git clone https://github.com/qualcomm/QAIC.git
cd QAIC/src
cabal update
make
The compiled binary will be available in the dist-newstyle directory.
Step 1: Install the Haskell toolchain
Download and install GHCup (the Haskell toolchain installer):
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
Follow the on-screen instructions to complete the installation. You may need to restart your terminal or run source ~/.ghcup/env to update your PATH.
Step 2: Install GHC and Cabal
Install the specific versions required for QAIC:
ghcup install ghc 9.10.3
ghcup set ghc 9.10.3
ghcup install cabal 3.12.1.0
ghcup set cabal 3.12.1.0
Step 3: Install system dependencies
Install the required build tools:
sudo apt-get update
sudo apt-get install make git
Step 4: Build QAIC
Clone the repository and build the compiler:
git clone https://github.com/qualcomm/QAIC.git
cd QAIC/src
cabal update
make
The compiled binary will be available in the dist-newstyle directory.
Note: These instructions have been tested on Ubuntu versions up to Ubuntu 24.04.
QAIC supports cross-compilation for different target architectures and operating systems. The Makefile automatically detects your platform, but you can override it to build for different targets.
Prerequisites for Cross-Compilation:
Before cross-compiling, ensure you have GHC and Cabal tools either installed or cross-compiled for the target architecture. The build system requires the appropriate toolchain to generate binaries for the target platform.
Cross-Compilation Commands:
To build for a specific target architecture, use the TARGET_ARCH parameter:
# Build for ARM64 Linux
make TARGET_ARCH=aarch64-linux
# Build for x86_64 Windows
make TARGET_ARCH=x86_64-windows
# Build for ARM64 Windows
make TARGET_ARCH=aarch64-windows
# Build for x86_64 Linux
make TARGET_ARCH=x86_64-linux
Auto-Detection:
If you don't specify TARGET_ARCH, the Makefile will automatically detect your platform:
uname -m (x86_64, aarch64, arm64)PROCESSOR_ARCHITECTURE environment variableOutput Locations:
The compiled binaries are placed in architecture-specific directories:
Linux_ReleaseG/ship/qaicWinNT_ReleaseG/ship/qaic.exeThe binaries are also available in the dist-newstyle directory.
QAIC is licensed under the BSD-3-Clause-Clear License. See LICENSE.txt for the full license text.
Haskell
69.4%
C
27.5%
Makefile
2.9%
qaic, Qaic's Another Idl Compiler, is a command-line executable used to implement remote shared objects for the DSP Platform. A remote shared object is a software component whose functions are implemented on the DSP but can be invoked directly from an application running on the Application Processor (APPS). The application on APPS calls these functions as if they were local, without requiring knowledge of the function’s execution location or the implementation language used on the DSP. To enable this communication, the user interacts with a stub object generated by qaic. The stub marshals the input parameters into a shared wire format and transmits the data to the domain where the remote object is hosted - typically the DSP. On the DSP, the host environment integrates a skel object, also generated by qaic, that unmarshals the data, and invokes the requested method on the native object. This remote communication between APPS and DSP is facilitated by the FastRPC framework, which provides the underlying infrastructure for cross-domain function invocation 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. 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.
The inc folder contains standard IDL files that can be included in your IDL definitions:
This file defines the remote_handle64 interface for managing remote execution sessions on DSP domains. It provides:
open() - Opens a handle in the specified domain (aDSP, mDSP, or DEFAULT). Creates a session on first handle, including device initialization, PD creation on the DSP, and loading the shared object with the skeleton function.
<interface>_URI&_dom=<domain> or file:///<sofilename>?<interface>_skel_handle_invoke&_modver=1.0close() - Closes a handle. When the last handle is closed, the session is terminated and all resources are released.
This interface is fundamental for establishing and managing FastRPC communication sessions between the application processor and DSP domains.
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:
--map-dll or -mdll
Generate DLL mapping
--output-path=PATH or -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.
--standard-types or -st
Use standard C types (bool, uint16_t, int32_t, etc.) instead of legacy types (boolean, uint16, int32, etc.) in generated code. When this flag is used, the generated header files will include <stdint.h> and <stdbool.h> (if boolean types are used). See the "Basic built-in types" section for detailed type mappings.
--version or -v
Print the version of the compiler.
--help or -h
Print a brief help message.
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 stub and skel code is generated.
qaic foo.idl
The above command compiles foo.idl to a remote header file foo.h, along with the following stub and skel 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.
qaic -st foo.idl
The above command compiles foo.idl using standard C types. The generated foo.h will use types like bool, int32_t, uint32_t, int64_t, and uint64_t instead of legacy types like boolean, int32, uint32, int64, and uint64. The header will automatically include <stdint.h> and <stdbool.h> (if boolean types are used).
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. Ensure that armcc is available in the system's PATH. The preprocessor can be invoked using the following command:
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.
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.
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.
qaic identifies code comments as either Doxygen comments or ordinary comments. Doxygen comments are preserved in the generated header files and are generally preferred for documenting interface methods.
When a method is documented with the Doxygen syntax, qaic will attempt to translate the documentation to the target language in any output files.
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, stub and skeleton source files that are linked into the CPU and DSP modules to enable them to communicate via RPC calls.
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.
Interfaces may not directly inherit from more than one base interface.
The rout parameter modes are used instead of out.
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.
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 functional difference between the traditional out and the newer rout parameter attribute, as the size is statically known and does not need to 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.
Note that the traditional OMG IDL out and inout parameter modes are not currently supported by the compiler.
These are the current rules for when NULL is passed as an argument.
in parameters:
NULL when the associated length is 0.rout parameters:
NULL when the associated length is 0.must be equivalent type to long
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.
in parameters:
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:
rout buffersin handles:
in dmahandle handlesrout handles:
rout dmahandle handlesA sample IDL file is shown below to illustrate the use of common IDL constructs.
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
long 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 int __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.
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
long 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.
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);
This section details the mapping of IDL constructs to C types.
The following table lists the mapping of IDL basic types to C.
Default mapping (without -st flag):
| IDL Type | C Type |
|---|---|
| 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 |
| 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) |
With -st or --standard-types flag:
When the -st flag is used, QAIC generates code using standard C types instead of legacy types. The following types are affected:
| IDL Type | C Type (with -st) |
|---|---|
| int8 | signed char |
| uint8 | unsigned char |
| int16 | signed short |
| uint16 | unsigned short |
| int32 | int32_t |
| uint32 | uint32_t |
| long long | int64_t |
| unsigned long long | uint64_t |
| int64_t | int64_t |
| uint64_t | uint64_t |
| boolean | bool |
| dmahandle | int (handle), uint32_t (offset), uint32_t (length) |
When using -st, the generated header files will include:
#include <stdint.h> for standard integer types#include <stdbool.h> for boolean type (if boolean is used in the IDL)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.
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
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
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);
Each method of an interface is mapped as a function.
in parameterin 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 parameterrout 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 parameterinrout 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);
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;
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 are not supported at this time.
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 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.
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);
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);
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);
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;
};
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];
};
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.
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);
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);
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.
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.
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;
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)
This section guides you through building the QAIC compiler from source using GHC 9.10.3 and Cabal 3.12.1.0.
Step 1: Install the Haskell toolchain
ghcup install ghc 9.10.3
ghcup set ghc 9.10.3
ghcup install cabal 3.12.1.0
ghcup set cabal 3.12.1.0
Step 2: Install Cygwin
Step 3: Build QAIC
Open the Cygwin terminal and run:
git clone https://github.com/qualcomm/QAIC.git
cd QAIC/src
cabal update
make
The compiled binary will be available in the dist-newstyle directory.
Step 1: Install the Haskell toolchain
Download and install GHCup (the Haskell toolchain installer):
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
Follow the on-screen instructions to complete the installation. You may need to restart your terminal or run source ~/.ghcup/env to update your PATH.
Step 2: Install GHC and Cabal
Install the specific versions required for QAIC:
ghcup install ghc 9.10.3
ghcup set ghc 9.10.3
ghcup install cabal 3.12.1.0
ghcup set cabal 3.12.1.0
Step 3: Install system dependencies
Install the required build tools:
sudo apt-get update
sudo apt-get install make git
Step 4: Build QAIC
Clone the repository and build the compiler:
git clone https://github.com/qualcomm/QAIC.git
cd QAIC/src
cabal update
make
The compiled binary will be available in the dist-newstyle directory.
Note: These instructions have been tested on Ubuntu versions up to Ubuntu 24.04.
QAIC supports cross-compilation for different target architectures and operating systems. The Makefile automatically detects your platform, but you can override it to build for different targets.
Prerequisites for Cross-Compilation:
Before cross-compiling, ensure you have GHC and Cabal tools either installed or cross-compiled for the target architecture. The build system requires the appropriate toolchain to generate binaries for the target platform.
Cross-Compilation Commands:
To build for a specific target architecture, use the TARGET_ARCH parameter:
# Build for ARM64 Linux
make TARGET_ARCH=aarch64-linux
# Build for x86_64 Windows
make TARGET_ARCH=x86_64-windows
# Build for ARM64 Windows
make TARGET_ARCH=aarch64-windows
# Build for x86_64 Linux
make TARGET_ARCH=x86_64-linux
Auto-Detection:
If you don't specify TARGET_ARCH, the Makefile will automatically detect your platform:
uname -m (x86_64, aarch64, arm64)PROCESSOR_ARCHITECTURE environment variableOutput Locations:
The compiled binaries are placed in architecture-specific directories:
Linux_ReleaseG/ship/qaicWinNT_ReleaseG/ship/qaic.exeThe binaries are also available in the dist-newstyle directory.
QAIC is licensed under the BSD-3-Clause-Clear License. See LICENSE.txt for the full license text.
Haskell
69.4%
C
27.5%
Makefile
2.9%