Есть библиотека на plain C, которая работает с неким форматом файлов. Интерфейс простой:
typedef struct mylib {
int mylib_errno;
...
} *mylib_t;
mylib_t lib = mylib_open("filename", ...);
somecount = mylib_getsomecount(lib, ...);
if (somecount < 0)
errx(1, "cannot get some count: %s\n", mylib_strerror(mylib_getlasterror(lib)));
mylib_close(lib);
Т.е. в struct mylib хранится errno в котором могут быть как положительные значения (от системных вызовов и функций из libc) так и отрицательные от самой библиотеки.
Это отлично работает, но только когда в mylib_open ничего сложного не делается (тогда если возвращается NULL, это однозначно malloc вернул ENOMEM) и когда в качестве someval можно передать невозможное значение (ну это не особо проблема, потому что если нельзя, то можно передавать через указатель на манер stat(2)). Но вот нужно в mylib_open сделать что-то нетривиальное, и потом узнать что случилось. Вопрос: как лучше построить интерфейс в общем случае?
Вариант 1:
typedef int mylib_code_t;
mylib_code_t mylib_open(mylib_t *);
mylib_code_t mylib_getsomeval(mylib_t, int *)
Т.е. выходные данные всегда передавать через указатели, а возвращать всегда код ошибки (или MYLIB_OK). Это не нравится по той причине, что так пользователю вероятнее забыть что-то освободить, т.е. вызвать mylib_open(&lib) при уже открытом lib уже открыт. Все-таки с lib = mylib_open() это легче заметить. Далее, нельзя написать в одну строку struct mylib *lib = mylib_open() и общее несогласование интерфейса с обычными библиотечными функциями, которые возвращают данные а не коды.
Вариант 2:
Хранить свой глобальный errno. Описанных выше проблем нет, но имеем проблемы с многопоточными программами. С другой стороны, если есть портабельный способ так объявить mylib_errno, чтобы при сборке с потоками он был в TLS, а без потоков просто глобальным, ИМХО это было бы самое оно.
Вариант 3:
Никогда не делать в open ничего сложного. Если нужно, добавить отдельную функцию, к вызову которой хэндл библиотеки уже будет выделен и будет куда сохранить ошибку. Лишняя строчка при инициализции, и по-моему, это единственный минус. Не так красиво, как вариант 2.
Идеи? Особенно интересует второй вариант. Есть мысли про установку обработчика ошибки + setjmp, но а коде который это использует невозможно разбираться, да и jmpbuf надо передавать опять таки. Т.е. это ухудшенный вариант 1.