-
Notifications
You must be signed in to change notification settings - Fork 0
Making declarations
Declaration is a single source of information about things included into your library.
You should enclose your declarations in /* */
code block. Look for declaration examples below.
All declarations consists of units, which is some keyword (like @member
) with a list of values separated by space. The first unit is always define declaration type. Units can be:
- singular: only one unit with such name can be presented in the declaration (e.g. return type of the function),
- plural: several units with such name can be used (e.g. members of the struct).
Following declaration types are supported: functions, structs, enums, flags, variables, typedefs.
This declaration defines a function placed. It supports the following singular units:
-
@return <return type>
: defines return type of the function.
Plural units:
-
@argument <argument type>
(optional): defines type of the single argument. All arguments should be placed in the same order they appear in C function declaration.
Declaration example:
/* @function function_name
* @return long
* @argument int
* @argument char
*/
long function_name(int number, char character);
Declared functions can be accessed from the Loader
instance by its name. Example:
lib = annotatec.Loader("lib.so", ["source1.h"])
lib.function_name(int_variable, char_variable)
This declaration defines a structure and its members as a type. Only one plural unit are available:
-
@member <member type> <member name>
: defines type and name of the structure's member.
Declaration example:
/* @struct my_structure
* @member int number
* @member char character
*/
typedef struct {int number, char character} my_structure_t;
Declared struct can be used as a type. Example:
inited_struct = lib.my_structure(
0, # int number
'a' # char character
)
Now this object can be passed to any function which takes such structure. For example, you have:
/* @function take_struct
* @return void
* @argument my_structure
*/
void take_struct(my_structure_t *struct) { ; }
This function can be called by
lib.take_struct(ctypes.pointer(inited_struct))
This declaration allows you to use enum
objects in your Python. Singluar units:
-
@type <enum type>
(optional): enumerators is usuallyint
. But just in case you need different type, your enumerator value can be casted to anything you need.
Plural units:
-
@member <name> <value>
: defines a single enumerator.<value>
can be anything executable to number. For example:5
,0b001
,(1 << 3)
or0xA
. If you need to use spaces, be sure your value is bracket-enclosed (e.g.(1 + 2)
).
Consider the following code:
/* @enum mode
* @member mode1 1
* @member mode3_long_name 3
* @member mode8_complicated_expression (1 << (3 ^ 5))
*/
typedef enum {
mode1 = 1,
mode3_long_name = 3,
mode8_complicated_expression = (1 << (3 ^ 5))
} mode_t;
/* @function enum_function
* @return int
* @argument mode
*/
int enum_function(mode_t mode) {
if (mode == mode1) return 10;
else
if (mode == mode3_long_name) return 30;
else
if (mode == mode8_complicated_expression) return 80;
}
When compiled into dynamic library and parsed with annotatec, it can be used like this:
lib.enum_function(lib.mode.mode1) # 10
lib.enum_function(
lib.mode.mode3_long_name) # 30
lib.enum_function(
lib.mode.mode8_complicated_expression) # 80
Flags can combine several options (mostly boolean ones) inside one variable. Flags can be combined using OR operator (|
) or addition (+
): flag1 | flag2
, flag1 + flag2
.
Imagine you're making some function which opens an Internet connection and may take the following options:
-
RETRY_ON_ERROR
: if turned on, connect attemption will be made several times, -
RECONNECT_ON_ERROR
: if turned on, connection will be reestablished in case of disconnection, -
USE_SECURE
: if turned on, some sort of secure connection will be used.
In order to encage states of these options into one variable, their values should not overlap at the bit-level (just like in bitmask).
Singular units:
-
@type <type name>
: defines type used for flag variable.
Plural units:
-
@member <flag name> <flag value>
: defines name and the value of the flag.<flag value>
can be something executable just like in the @enum declaration.
Consider the following C code:
/* @flags connection_mode
* @member RETRY_ON_ERROR
* @member RECONNECT_ON_ERROR
* @member USE_SECURE
*/
typedef char connection_mode_t;
const connection_mode_t RETRY_ON_ERROR = (1 << 0);
const connection_mode_t RECONNECT_ON_ERROR = (1 << 1);
const connection_mode_t USE_SECURE = (1 << 2);
/* @enum connection_result
* @member CONNECTION_OK 1
* @member CONNECTION_FAIL 2
*/
typedef enum {OK = 1, FAIL = 2} connection_result_t;
/* @function connect
* @return connection_result
* @argument connection_mode
*/
connection_result_t connect(connection_mode_t mode) {
// ... do some logic
return fail ? FAIL : OK;
}
It can be used in the Python wrapper like so:
result = lib.connect(
lib.connection_mode.RETRY_ON_ERROR | ilb.connection_mode.USE_SECURE)
if result == lib.connection_result.OK:
print("success!")
elif result == lib.connection_result.FAIL:
print("fail!")
else:
print("I saw an UFO")
This declaration allows you to use global variables defined in the dynamic library. The only unit available is singular:
-
@type <variable type>
: defines type of the variable.
Consider the following example. lib.h
/* @variable ERROR_LEVEL
* @type int
*/
extern int ERROR_LEVEL;
lib.c
source:
int ERROR_LEVEL = 0;
/* @function do_something
* @return void
*/
void do_something(void) {
// ...some logic
if (fail) ERROR_LEVEL = 1;
else ERROR_LEVEL = 0;
}
/* @function reset_error
* @return void
*/
void reset_error(void) {
ERROR_LEVEL = 0;
}
The code above introduces a variable which can store the code of the last error happened. It can be accessed from Python like so:
print(lib.ERROR_LEVEL) # can be anything
lib.do_something() # this function change the
# value of ERROR_LEVEL
print(lib.ERROR_LEVEL) # "0" or "1" will be printed
lib.reset_error() # reset ERROR_LEVEL to 0
print(lib.ERROR_LEVEL) # "0" will be printed
By the way, such implementation is good to combine with @enum. For example:
/* @enum error
* @member ok 0
* @member error1 1
* @member error2 2
*/
typedef enum {OK = 0, ERROR1 = 1, ERROR2 = 2} error_t;
/* @variable ERROR_LEVEL
* @type error
*/
error_t ERROR_LEVEL = OK;
Such declaration allows you to create an alias for the type. Only one singular unit is allowed:
@from_type <type name>
Consider the following C code:
/* @typedef some_other_type
* @from_type int
*/
typedef int some_other_type;
Such declaration allows you to use some_other_type
literal in other declarations:
/* @function some_function
* @return some_other_type
* @argument some_other_type
*/
some_other_type some_function(some_other_type);
Now your type some_other_type
is compatible with int
, so you shouldn't cast your number to some_other_type
before passing to the function which accepts it. But anyway, it can be done just for fun 😄
variable = lib.some_other_type(5)
lib.some_function(variable) # but lib.some_function(5)
# will be fine as well