C language mapping

This page describes the C code generated by iopc -l c: how IOP types map to C types. The runtime APIs that operate on these types are documented in C runtime library.

1. Generated files

Each IOP package <pkg> produces four files:

<pkg>-t.iop.h

The C structures corresponding to the IOP types. Never include it directly — include <pkg>.iop.h.

<pkg>-tdef.iop.h

The C typedefs for the types and their array types, and the enum definitions. Never include it directly either.

<pkg>.iop.h

The public header: includes the two others and declares the descriptors and helpers.

<pkg>.iop.c

The type descriptions (reflection data) used by the IOP runtime.

2. Naming conventions

C identifiers are derived by converting CamelCase to snake_case and prefixing with the package name and two underscores:

  • type FooBar of package testtestfoo_bart;

  • its descriptor (reflection object) → testfoo_bars (__e for enum descriptors);

  • enum values are prefixed with the upper-snake-case enum name (or the @prefix value): MyEnum.VAL_1MY_ENUM_VAL_1.

The @ctype(other__t) attribute additionally emits typedef aliases (and helper-macro aliases) under the given name:

typedef foo__my_enum_a__t e_a__t;       /* from @ctype(e_a__t) */
#define e_a_to_str(...)  foo__my_enum_a__to_str(__VA_ARGS__)

3. Enums

package test;

enum MyEnum {
    VAL_1,
    VAL_2,
};
typedef enum test__my_enum__t {
    MY_ENUM_VAL_1,
    MY_ENUM_VAL_2,
} test__my_enum__t;

Utility helpers are generated/available for conversions, e.g. iop_enum_to_str(test__my_enum, 1)"VAL_2" (see lib-common/iop.h and iop-macros.h).

4. Scalar fields

Mandatory scalars map directly:

IOP C IOP C

byte

int8_t

ubyte

uint8_t

short

int16_t

ushort

uint16_t

int

int32_t

uint

uint32_t

long

int64_t

ulong

uint64_t

bool

bool

double

double

Optional scalars use opaque option types manipulated through the OPT_* macros of lib-common/iop-macros.h:

if (OPT_ISSET(st.my_opt_int)) {
    printf("set: %d\n", OPT_VAL(st.my_opt_int));
}

5. Strings, bytes, xml

All three map to lstr_t (lib-common/str-l.h). A mandatory string/bytes/xml must not hold a NULL pointer — use LSTR_EMPTY_V for the empty value. For an optional field, LSTR_NULL_V (NULL .s pointer) means absent, which is distinct from empty:

test__my_struct__t foo = {
    .a = LSTR_IMMED("plop"),   /* mandatory string */
    .b = LSTR_NULL_V,          /* optional bytes, absent */
};

6. Repeated fields

Arrays are structures with two public fields: tab (pointer to the elements — pointers to instances for class element types) and len. Typedefs are provided: iop_array_<type>_t for scalar element types, <pkg><type>array_t (or IOP_ARRAY_T(pkg__type)) for named types. The containers are tab_for_each_*-compatible. The runtime never reallocates or frees an array it did not allocate; emptiness is len == 0.

7. Struct, union and class fields

  • mandatory struct/union field → inlined value;

  • mandatory reference field (Type&) → pointer, never NULL once the object is built;

  • optional struct/union field → pointer, NULL = absent;

  • class-typed field → always a pointer, whatever the cardinality (NULL = absent for optional fields, invalid-but-transitory for mandatory ones);

  • mandatory void fields do not exist in the C struct; optional void fields are a presence boolean.

8. Unions

A union maps to a tagged struct that must be manipulated through the union macros (lib-common/iop-macros.h):

test__my_union__t u = IOP_UNION_CST(test__my_union, c, LSTR_IMMED("plop"));

IOP_UNION_SWITCH(&u) {
  IOP_UNION_CASE_P(test__my_union, &u, a, vp) {
      printf("a: %d\n", *vp);
  }
  IOP_UNION_CASE(test__my_union, &u, b, v) {
      printf("b: %jd\n", v);
  }
  IOP_UNION_CASE(test__my_union, &u, c, v) {
      printf("c: %*pM\n", LSTR_FMT_ARG(v));
  }
}
IOP_UNION_CASE expands to a for construct: never break or continue out of an IOP_UNION_SWITCH.

Selection and extraction helpers include IOP_UNION_SET, IOP_UNION_GET, IOP_UNION_CST, …

9. Classes

package test;

class A1 {
    static string name = "a1";
    int a = 10;
};

class B1 : 1 : A1 {
    string b;
};
typedef struct test__a1__t {
    const iop_struct_t *__vptr;
    int32_t a;
} test__a1__t;
IOP_CLASS(test__a1);

typedef struct test__b1__t {
    struct {
        const iop_struct_t *__vptr;
        int32_t a;            /* fields of test__a1__t */
    };
    lstr_t b;
} test__b1__t;
IOP_CLASS(test__b1);
  • vptr points to the descriptor of the instance’s concrete class; never modify it. iop_init(testb1, &v) sets both the defaults and __vptr.

  • Parent fields are embedded in a leading anonymous struct, making a child pointer layout-compatible with its ancestors.

  • IOP_CLASS(…​) generates the class helpers (structs and unions use IOP_GENERIC(…​)).

  • Beware of class containers: class fields are pointers, so iop_init of the container leaves them NULL; mandatory ones must be set before packing.

9.1. Casts and type tests

iop_obj_is_a(ptr, testb1) tests the dynamic type; iop_obj_vcast(testb2, ptr) / iop_obj_ccast(test__b2, ptr) (non-const/const) downcast with runtime checking:

if (iop_obj_is_a(a1, test__b2)) {
    const test__b2__t *b2 = iop_obj_ccast(test__b2, a1);
    ...
}

9.2. Switch on dynamic type

IOP_CLASS_SWITCH(name, obj) matches the nearest ancestor case; IOP_CLASS_EXACT_SWITCH(obj) matches the exact class only. Cases use IOP_CLASS_CASE(type, obj, var) / IOP_CLASS_CASE_CONST, or plain case IOP_CLASS_ID(type):. Every IOP_CLASS_SWITCH must contain IOP_CLASS_DEFAULT(name); IOP_CLASS_EXACT_DEFAULT() is optional for the exact variant. The switch may only contain classes of the instance’s inheritance tree.

9.3. Static fields

Read class constants with iop_get_cvar(obj, "name") and its variants (iop_get_cvar_cst, …); lookup walks up the inheritance chain.

10. Attribute effects in C

  • @ctype(xt): emits typedef + macro aliases under x (the argument must end in t);

  • @prefix(A): the enum constants are emitted as A_* (the C type name is unchanged);

  • @private on a field: the field exists in C like any other (privacy is enforced by the encoders and decoders);

  • @noReorder: prevents iopc from reordering the C struct members to reduce padding;

  • constraint attributes: generate per-field check functions registered in the descriptors (used by iop_check_constraints, see C runtime library).