Using nunavut to generate c object

nunavut generate too many things with different name, and I think why not generate c object to abstract each protocol.
like:

typedef struct CanardBaseHandle {
	char *name;
	char *name_and_version;
	size_t extent;
	size_t serialize_buffer_size;
	CanardPortID port_id;
	CanardTransferKind transfer_kind;
	void (*initialize)(void* obj);
	int8_t (*serialize)(void* obj,
			  uint8_t* const buffer,
			  size_t *const inout_buffer_size_bytes);
	int8_t (*deserialize)(void* obj,
			    const uint8_t* const buffer,
			    size_t *const inout_buffer_size_bytes);
} CanardBaseHandle;

CanardBaseHandle uavcan_node_Heartbeat_1_0_handle = {
	.name = uavcan_node_Heartbeat_1_0_FULL_NAME_,
	.name_and_version = uavcan_node_Heartbeat_1_0_FULL_NAME_AND_VERSION_,
	.extent = uavcan_node_Heartbeat_1_0_EXTENT_BYTES_,
	.serialize_buffer_size = uavcan_node_Heartbeat_1_0_SERIALIZATION_BUFFER_SIZE_BYTES_,
	.port_id = uavcan_node_Heartbeat_1_0_FIXED_PORT_ID_,
	.transfer_kind = CanardTransferKindMessage,
	.initialize = uavcan_node_Heartbeat_1_0_initialize,
	.serialize = uavcan_node_Heartbeat_1_0_serialize,
	.deserialize = uavcan_node_Heartbeat_1_0_deserialize,
};

it easily to access variable by uavcan_node_Heartbeat_1_0_handle.
or generate source code by transfer kind to an object.

1 Like

Not sure, it almost sounds like you need C++ code generation here.

@scottdixon what do you think?

After canard protocol stack receive done, it will invoke user callback, pass CanardBaseHandle as parameter.
so that user can easily to use related parameter.

I agree it would be nice to have some encapsulation on the generated C code. I was considering eventually diving into the YAML to do something simllar. The generated interfaces are currently very flexible, but somewhat error-prone.

Each subscribe message have a user callback.

1 Like

TLDR;

I’m not inclined to modify the current C generation templates to use this style but I would support a new language option that would result in generating this type of C. I’m not going to do that work myself before C++ is out and I’m more inclined to look at expanding language support (e.g. Rust, Ada, Go, Javascript, Python) before revisiting C.

TL

While I’m not against this proposal it does have drawbacks from a high-assurance software standpoint mostly due to all the pointers you have to use. I’ve done this type of C before and it’s, basically, just a half implementation of Objective-C (I like to call this “Objecty-C”). For example:

#include <stdint.h>
#include <stddef.h>

struct SerializableType;

typedef int8_t (*SerializeFunctionType)(
    struct SerializableType* self,
    void* obj,
	uint8_t* const buffer,
	size_t *const inout_buffer_size_bytes);

typedef struct SerializableType
{
    SerializeFunctionType serialize;
} Serializable;

int8_t uavcan_node_Heartbeat_1_0_serialize(
    struct SerializableType* self,
    void* obj,
	uint8_t* const buffer,
	size_t *const inout_buffer_size_bytes)
{
    if (!self)
    {
        //WTF?
    }
    // implementation omitted
    return 0;
}

// Might as well use the Objective-C convention for object initialization here
Serializable* init_uavcan_node_Heartbeat_1_0(Serializable* storage)
{
    if (storage)
    {
        storage->serialize = uavcan_node_Heartbeat_1_0_serialize;
    }
    return storage;
}

int main()
{
    uint64_t fake_obj;
    uint8_t buffer[5];
    size_t buffer_size_bytes = 5;

    Serializable s;
    if (!init_uavcan_node_Heartbeat_1_0(&s))
    {
        // WTF?
        return -1;
    }
    if (!s.serialize)
    {
        // WTF?
        return -2;
    }
    s.serialize(&s, &fake_obj, buffer, buffer_size_bytes);
    return 0;
}

Whereas the current C code is annoying because we all hate to type, the compiler is able to assure us that all the function calls are valid and that most of the required arguments (where not pointers) are present. In the Objecty-C example above you see the WTF comments where we now have to add error handling that should never occur and which is therefore really hard to cover in tests. We’re now doing a bunch of runtime checks with runtime failures. You can (i.e. you really should) add static analysis in to augment the compiler for this style of C but that isn’t a portable solution.

If you are not writing high-assurance code then this is a fun style to use, I admit, and you tend to just omit the pointer checks on self and trust the initializer to always populate the function pointer list. So I’m not against this existing but it is not appropriate as the default and/or only style of C for Nunavut to generate.

1 Like

@scottdixon

Everything can be pointed。
its not the point is not safe, its depending who use it.
after stack protocol received the message, its already know which kind of message I have received. so that protocol stack tell user, here is my tool(a point to a struct that include serialize and deserialize). user can use this tool or deserialize by its self.

I think extend struct CanardRxSubscription to be that tool.
nunavut can be auto generate struct CanardRxSubscription object, if user want subscribe it, just register it.

Scott meant that this approach circumvents the type system of the language, which comes at a cost. It doesn’t matter who uses it, it is unsafe.

Maybe you could consider implementing your own C templates based on the upstream ones? Then you can use them with Nunavut with --templates.