Soy un poco "bienes dañados" cuando se trata de este tema. Solía diseñar y mantener API bastante grandes para telecomunicaciones integradas. Un contexto donde no puedes dar nada por hecho. Ni siquiera cosas como variables globales o TLS. A veces, incluso los almacenamientos intermedios de montón aparecen que en realidad están dirigidos a la memoria ROM.
Por lo tanto, si está buscando un "mínimo común denominador", también puede pensar qué construcciones de lenguaje están disponibles en su entorno objetivo (es probable que el compilador acepte cualquier cosa dentro del estándar C, pero si no es compatible, el enlazador dirá que no).
Habiendo dicho eso, Siempre iré por la alternativa 1. En parte porque (como otros han señalado), nunca se debe asignar memoria para el usuario directamente (un enfoque indirecto se explica más abajo). Incluso si se garantiza que el usuario trabajará con C pura y simple, todavía podría, por ejemplo, usar su propia API de administración de memoria personalizada para rastrear fugas, registro de diagnóstico, etc. El apoyo para estrategias como esa es comúnmente apreciado.
La comunicación de error es una de las cosas más importantes cuando se trata de una API. Dado que el usuario probablemente tenga formas distintas de manejar los errores en su código, debe ser lo más constante posible acerca de esta comunicación a través de la API. El usuario debería ser capaz de ajustar el manejo de errores hacia su API de manera consistente y con un código mínimo. En general, siempre recomendaría usar códigos de enum claros o define/typedefs. Yo personalmente prefiero typedef: ed enumeraciones:
typedef enum {
RESULT_ONE,
RESULT_TWO
} RESULT;
.. porque proporciona seguridad de tipo/asignación.
Tener un obtener el último error función también es bueno (requiere almacenamiento central sin embargo), personalmente lo uso únicamente para proporcionar información adicional sobre un error ya reconocido.
El nivel de detalle de la alternativa 1 se puede limitar al hacer compuestos simples como esto:
struct Buffer
{
unsigned long size;
char* data;
};
A continuación, el API puede verse mejor:
ERROR_CODE func(params... , Buffer* outBuffer);
Esta estrategia también se abre para más elaborado mecanismos. Digamos por ejemplo, usted debe ser capaz de asignar memoria para el usuario (por ejemplo, si necesita cambiar el tamaño de la memoria intermedia), entonces se puede proporcionar una aproximación indirecta a esto:
struct Buffer
{
unsigned long size;
char* data;
void* (*allocator_callback)(unsigned long size);
void (*free_callback)(void* p);
};
Por supuesto, el estilo de este tipo de construcciones es siempre abierto para un debate serio.
¡Buena suerte!
¿No debería el tipo de buffer ser char **? Además, ¿por qué necesita un buffer_size en la opción uno y no en la opción dos? – mweerden
Pasa un búfer preasignado como el parámetro in y espera que la función llamada lo complete con un texto de error. – sharptooth
Ok, pero luego buffer_size no necesita ser un puntero, ¿verdad? – mweerden