LINUX.ORG.RU

Ищу гайдлайны по написанию C-API

 ,


0

4

Нужно написать API к либе на Rust (ну или C++, суть не меняется). Проблема в том, что в сишке я 0. Посоветуйте хорошие гайдлайны по написанию API.

Интересует:

  • именование структур, полей, функций
  • кто должен отвечать за выделение/освобождение памяти? Нужны ли свои init/destroy или пусть человек сам мучается?
  • какие типы лучше использовать? int vs int32_t.
  • как возвращать ошибки?
    • void method(char **error)
    • const char* method()
    • int/char/enum?
  • нужны ли фукнции для инициализации нетривиальных структур?
  • и тд.

Нужно чтобы сишники камнями не закидали.

★★★★★

Я не уверен, но возьми любую библиотеку, например libcurl или ncurses. Там есть чёткое апи.

itn ★★★
()

На каждый вопрос есть несколько ответов, какждый из них правильный в рамках какой-то субкультуры разработки.

именование структур, полей, функций

обычно всё змеиным_кейсом, но иногда ИменаСтруктурТакие

кто должен отвечать за выделение/освобождение памяти?

Иногда делается функция make_thing, которая выделяет память и инициализирует thing (аналог new), иногда - init_thing, которая работает с уже выделенной памятью,

Нужны ли свои init/destroy или пусть человек сам мучается?

Нужны.

какие типы лучше использовать? int vs int32_t.

Во внешних интерфейсах - с явно указанной разрядностью.

как возвращать ошибки?

-1 или NULL; для тех, кто угорел по ядру - в младших битах невалидного указателя можно передавать код ошибки.

Есть еще стиль errno/SetLastError, но лично я желаю всего плохого тем, кто будет его продвигать.

нужны ли фукнции для инициализации нетривиальных структур?

Это тот же вопрос, что и выше.

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 1)

Открой какой-нибудь gl.h, у тебя он на компе очевидно есть, и делай примерно так же.

А так:

1) Объявляй typedef struct structName_s structName_t, чтобы потом не вводить постоянно слово struct при объявлении. :)

2) Ну ты можешь дать возможность разработчику задавать свою функцию-аллокатор.

3) Где какие надо, там такие и используй. Если размер не важен, то может и не стоит пихать повсюду типы чёткого размера.

4) Ну сам думай. Я бы возвращал код ошибки и предоставлял аналог strerror.

5) Простых? Что ты там инициализировать собрался?

a1batross ★★★★★
()

именование - я за snake_case, для опенсорсников он привычнее чем CamelCase.

API - если ты работаешь с динамической памятью, то определись, выделяешь ли ты её сам, либо ждёшь что тебе приедет готовый буфер известного размера. Кто эту память должен освобождать. Определись и используй одинаковый подход.

Для ошибок - возвращай int и предоставь аналог strerror которая будет по номеру возвращать указатель на статическую строку с описанием. Эту строку освобождать пользователю не надо, она вообще не динамическая.

Инициализацию отдельную делать разве что если ты реализацию структуры прячешь «под капот» и доступа к полям напрямую не даёшь.

Типы по максимуму из stdint.h, typedef по желанию, но лично я против их использования без необходимости.

Dark_SavanT ★★★★★
()
Ответ на: комментарий от a1batross

Кстати говоря, это относится к любым «своим» типам. То бишь так же для enum и union.

a1batross ★★★★★
()

https://matt.sh/howto-c https://www.gitbook.com/book/hintjens/scalable-c/details

Обе ссылки не без возможности поспорить, но в целом полезны.

кто должен отвечать за выделение/освобождение памяти?

Зависит от задачи. Если задача далека от низкоуровневого байто-совокупления, где могут потребоваться неординарные аллокаторы, делайте struct mything * mything_new();, которая выделяет память, и парную к ней void mything_free(struct mything *); (потому что библиотека и пользовательская программа могут иметь две разных кучи и два разных набора malloc и free). Если в задаче каждая инструкция процессора на счету (или речь идёт о микроконтроллерах), пользователю придётся раскрыть содержимое структуры (чтобы компилятор знал её размер) и предоставить void mything_init(struct mything*).

нужны ли фукнции для инициализации нетривиальных структур?

Да. Если содержимое структуры раскрыто пользователю, а для очень часто используемых случаев структуру можно инициализировать одним большим выражением в скобках, можете дополнительно дать пользователю #define MYTHING_DEFAULT_INITIALIZER { ... }, но увлекаться этим не стоит.

Нужны ли свои init/destroy или пусть человек сам мучается?

Обязательно нужны. Если можете, постарайтесь обойтись только struct mything; и функциями, принимающими указатели, в публичных заголовочных файлах и определяйте содержимое структуры только внутри библиотеки. Так больше потенциал для дальнейшего перепиливания реализации. Наоборот (по возможности), считайте пользовательский заголовочный файл Священным Писанием и меняйте его как можно реже (в идеале - только добавляйте функции).

Почитайте Дреппера про версионирование библиотек.

какие типы лучше использовать? int vs int32_t.

Типы для чего? Используйте типы явно заданного размера для вещей, которым требуется явно заданный размер (типа uint16_t номера порта в TCP). Используйте системно-специфичные типы для системно-зависимых вещей: size_t для смещений от начала массива, ptrdiff_t для разности между указателями, etc. Если число влезает в гарантированные стандартом рамки для более древних типов (char, short, int, long), теоретически, можно использовать и их (пример: номер канала в 16-канальном генераторе сигналов), но это считают плохим тоном. Возможно, стоит задуматься об enum.

как возвращать ошибки?

Если ошибок не очень много, заведите enum mything_error. Если жизненно важно вернуть строку с ошибкой, заполняйте данный пользователем буфер или возвращайте указатель на статические константные данные: при обработке ошибок меньше всего хочется ещё и освобождать память под строку с ошибкой.

anonymous
()
Ответ на: комментарий от Dark_SavanT

Для ошибок - возвращай int

Тоесть, return для ошибок, а реальный результат всегда через указатель в аргументах функции?

makoven ★★★★★
()
Ответ на: комментарий от Dark_SavanT

Точнее так - для си однозначно хорошего решения нет вообще.

Dark_SavanT ★★★★★
()
Ответ на: комментарий от Dark_SavanT

по номеру возвращать указатель на статическую строку с описанием
статическую

Я понимаю что сишка и строки не совместимы, но у меня содержимое ошибок динамическое, поэтому я могу возвращать только строку.

Почти все структуры - это opaque pointer, так как они хранят растовские структуры.

Напомнили о ещё паре вопросов:

  • typedef struct vs struct? Мне первое больше нравится, так как меньше писанины.
  • Под какой стандарт составлять? Или в хедерах ничего в C99 не поменялось? У меня только opaque pointer'ы и функции. Больше ничего.
RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

но у меня содержимое ошибок динамическое

Насколько динамическое? Отдельную структуру для описания ошибки описать невозможно? Пишите сообщение в предоставленный пользователем буфер известного размера.

typedef struct vs struct? Мне первое больше нравится, так как меньше писанины.

Вопрос вкуса. Либо typedef struct { ... } mytype, либо struct mytype { ... }.

Под какой стандарт составлять? Или в хедерах ничего в C99 не поменялось? У меня только opaque pointer'ы и функции. Больше ничего.

C99 добавил stdint, stdbool и complex (к вопросу о типах возвращаемых значений). C11 не добавил ничего из того, что могло бы относиться к функциям opaque pointers на структуры Rust.

anonymous
()
Ответ на: комментарий от anonymous

Отдельную структуру для описания ошибки описать невозможно?

Нет.

Пишите сообщение в предоставленный пользователем буфер известного размера.

Не влезет.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

Нужно больше информации. Желательно, пример.

В худшем случае, конечно, никто не мешает сделать char* mytype_geterror(mytype*) и потребовать у пользователя его потом освобождать, но это неудобно использовать.

anonymous
()
Ответ на: комментарий от RazrFalcon

поэтому я могу возвращать только строку.

Это плохо. Либо не отпускай строку от растового менеджера памяти, либо прямо говори, что «эту строку грохнуть самостоятельно». Оба варианта очень плохие.

Подумай над тем, чтобы возвращать строки из статического массива, без динамики. Это сильно упростит API.

Dark_SavanT ★★★★★
()

именование структур, полей, функций

snake_case,и никакой системной венгерской!

кто должен отвечать за выделение/освобождение памяти? Нужны ли свои init/destroy или пусть человек сам мучается?

Поскольку у тебя в Rust сильно своя атмосфера, и утечка памяти не является нарушением memory safety, то делай _create _destroy или аналоги.

Так же рикамендую сеттеры и геттеры на каждое свойство, внутри оберни свои структуры Mutex<t>, если предполагается их изменение из нескольких потоков.

какие типы лучше использовать? int vs int32_t.

stdint.h stdbool.h c явным указанием размерности, opaque ptr для указателей.

Не забудь extern «C» {} в случае __cplusplus

как возвращать ошибки

Лучше всего возвращать номер ошибки, для которого сделать enum, если там что-то более сложное - можно сделать что угодно, вплоть до константного JSON, вопрос в том как под него память выделить: выделишь у себя, быдлокодеры смогут порушить твои данные по указателью, а для выделения у юзера надо колбек городить или типа того.

нужны ли фукнции для инициализации нетривиальных структур?

обязательно, см. выше...

shkolnick-kun ★★★★★
()
Ответ на: комментарий от RazrFalcon

Проблемы сишки меня мало волнуют

Если бы это было так, не было бы этой темы, так что... поменьше высокомерия.

А если твои сообщения об ошибке длинны, как «Война и Мир», и не помещаются ни в буфер статического размера, ни в буфер, выделяемый пользователем, сделай функцию write_error_to(err, fd).

tailgunner ★★★★★
()
Ответ на: комментарий от shkolnick-kun

А да, opaque ptr лучше не используй!

Сделай пул ресурсов пряму внутри своей либы и перевыделяйпамять, если ресурсы закончатся.

А вместо указателей - выдавай индексы в пуле или еще что!

Анаогично можно поступить с ошибками: выделяешь объект у себя, выдаешь индекс если все плохо или делаешт универсальные геттеры + _destoy...

shkolnick-kun ★★★★★
()
Последнее исправление: shkolnick-kun (всего исправлений: 1)
Ответ на: комментарий от tailgunner

Ну у меня все ошибки из разряда показать пользователю. Обрабатывать там него. Если **error не NULL - уже всё плохо.

write_error_to(err, fd)

Можно поподробней?

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от shkolnick-kun

Зачем так сложно? У меня C-API для галочки. Сомневаюсь что оно кому-то, кроме меня, понадобится.

Если уж понадобится моя либа - пусть на расте лабают.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

Программе на C в первую очередь будет бесполезна строка-простыня с описанием ошибки, сгенерированным error-chain. Ведь не парсить же её, чтобы понять, что сделано не так?

Определите несколько кодов состояния, на которые можно реалистично среагировать со стороны C-кода, и возвращайте их. Строку с ошибкой отдавайте опционально в виде принадлежащего Rust указателя с возможностью вызвать от неё strdup().

anonymous
()
Ответ на: комментарий от shkolnick-kun

Аллергия у меня на сишку. Ничего не могу поделать.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

Тогда пусть 0-завершённая строка с ошибкой принадлежит Rust, код на C может вызвать printf или скопировать при помощи strdup(), если ему очень захочется, а менеджер памяти Rust её потом освободит.

anonymous
()
Ответ на: комментарий от RazrFalcon

Это вообще единственный способ перенаправить куда-то данные серьезно неопределенной длины. В терминал, в сеть, в память - в любое место, для которого у тебя есть fd.

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 2)

именование структур, полей, функций

struct something;
struct something *something_init(...);
void something_destroy(struct something *);
uint32_t something_get_field(struct something *);
void something_set_field(struct something *, uint32 value);
struct my_error *something_perform(struct something *, ...);

кто должен отвечать за выделение/освобождение памяти?

Библиотека.

Нужны ли свои init/destroy или пусть человек сам мучается?

Нужны.

какие типы лучше использовать? int vs int32_t.

Не представляю, зачем нужны типы без чёткого указания их размера.

как возвращать ошибки?

Лучше всего как объект, у которого есть тип, возможно какие-то дополнительные поля и метод преобразования в строку. Что-то вроде

struct my_error;
uint32_t my_error_get_type(struct my_error *);
char *my_error_get_description(struct my_error *);
uint32_t my_error_sql_code(struct my_error *); // для type == SQL_ERROR
Legioner ★★★★★
()
Ответ на: комментарий от RazrFalcon

Нинада возвращать строку. Возвращай какой-то номер ошибки (заведи enum с ними) и, предоставь пользаку определять свой собственный эррор хендлер, в который ты передашь рабочий контекст, из которого пользак сможет (ты опишешь как) получить нужные данные для формирования понятной причины случившегося. Иначе, если пользак не передал кастомный, юзай свой дефолтный хендлер, который тупо гадит в stdout(err).

deep-purple ★★★★★
()
Последнее исправление: deep-purple (всего исправлений: 1)
Ответ на: комментарий от RazrFalcon

typedef struct vs struct? Мне первое больше нравится, так как меньше писанины.

typedef struct не нужен (да и большинство typedef-ов тоже). Пользователь API должен понимать, что работает со struct (или с указателем) без лишних лазаний в потроха. Меньше писанины в данном случае это не плюс. Исключение разве что какое-нибудь WinAPI, которое программисты изучают годами и потом годами же применяют. Там можно ввести свой словарь приставок/суффиксов. А в общем случае так делать не стоит.

Legioner ★★★★★
()
Ответ на: комментарий от tailgunner

-1 или NULL; для тех, кто угорел по ядру - в младших битах невалидного указателя можно передавать код ошибки.

это не очень хорошо. Что толку от ошибки, когда непонятно от чего она произошла? Это чуть лучше чем сегфолтнуться (программист может сделать штатное завершение работы программы в общем случае). Ну и потом, может так быть, что у тебя не void а что-то другое. Так что надо что-то осознанное возвращать.

peregrine ★★★★★
()

Нужно написать API к либе на Rust (ну или C++, суть не меняется). Проблема в том, что в сишке я 0. Посоветуйте хорошие гайдлайны по написанию API.

тут вообще только один вариант: найти человека который 1)знает С 2) ему нужна (или хотя бы интересна) библиотека 3) «съел собаку» в предметной области.

потому что чтобы API(и библиотекой) пользовались надо прикидывать use-case. Чтобы это сделать надо знать окружение в котором будет типично использоваться библиотека. Неплохо знать API ближайших смежников и аналоги. Придётся ещё писать тесты и демки. Ещё и заранее думать как сопровождать всё это хозяйство.

Без знания и практики С вы это не сделаете и никакие «гайдлайны» тут не помогут (да их и нет. то что в теме нагородили - просто частные технологические приёмы)

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

C-API, пока что, использую только я. Так как мне нужно растовскую либу из Qt дёргать. И мне фиолетово какое у неё апи, главное чтобы работало.

Всё в публичном доступе - если кому-то будет нужно, пусть пришлёт патч. Просто я в си - дуб и хочется что-бы хедер был хоть каплю пристойным.

RazrFalcon ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.