Lo normal sería:
- tener una función de inicialización de la librería y una de desinicialización (aunque no hagan nada, pero tipicamente, pedir un buffer de datos internos y guardarlos en un puntero global; o mejor devolver una instancia de la librería, que internamente sería ese puntero a tu estructura de datos)
- lo mismo para cada instancia del dispositivo (equivalentes a un new/delete), que te devuelven un id de dispositivo (una entrada en una tabla en la estructura anterior o un puntero a la estructura del dispositivo). Lo primero te permite asegurar que un llamado a milibreria_cleanup(instancia) libera todo.
- nombres descriptivos: C no permite namespaces ni scope de clases en los nombres (ni overloads), así que te toca llamar a las funciones: mylib_i2c_read() por ejemplo. Lo mismo para los enums, por favor no definas OK, sino MYLIB_OK.
- usar typedefs. No devuelvas "int" como código de error, eso es horrible. Devolvé mylib_errcode_t. Y tratar de no comparar errcode < 0 sino aunque sea un #define que sea MYLIB_ISERROR(x) ((x) != MYLIB_OK) o algo así. Es más portable y más legible.
- indentar con un número lógico de espacios (ej. no 3) ;)
Una forma de verlo es que generes el código en C++ del wrapper que crearía el cliente. Y de ahí te crees las funciones de C necesarias. Por ejemplo:
namespace mylib {
class I2C
{
public:
I2C() : libinstance(MyLib::getInstance()), devinstance(mylib_i2c_new(libinstance))
{
if (!devinstance) {
throw something;
}
}
~I2C()
{
mylib_i2c_delete(libinstance, devinstance);
}
ssize_t read(char* buffer, size_t qty)
{
ssize_t qty_ret= 0;
if (MYLIB_ISERROR(mylib_i2c_read(libinstance, devinstance, buffer, qty, &qty_ret))) {
throw somethingelse;
}
return qty_ret;
}
private:
mylib* libinstance=nullptr;
mydev* devinstance=nullptr;
} // class I2C
} // namespace mylib