LINUX.ORG.RU

C++: передача коллекций из динамической библиотеки.

 ,


0

2

Есть у меня рабочий проект, который состоит из разделяемой библиотеки и утилит, которые выполняют прикладные задачи, дергая из неё функции. ПО пишется под Linux+Windows, и есть требования не использовать сторонние библиотеки. Из-за этого библиотека предоставляет не только функции, решающие конкретную проблему, но и всякие обёртки: рекурсивное сканирование каталога, чтение настроек из ini-подобного файла и т.д. И есть также требование, чтобы интерфейс библиотеки был максимально бинарно совместим: под Windows мы собираем в Visual Studio 2008, а другие проекты, юзающие библиотеку, могут быть собраны и в 2010, например; или в Линуксе мы собираем gcc + libstdc++, а клиенты библиотеки могут быть собраны чрез clang + libc++. Из-за этого интерфейс библиотеки оперирует только POD-типами данных и абстрактными классами с фабриками.

На примере функции сканирования каталога проиллюстрирую свою проблему. Не заморачиваясь на POD и совместимость, я бы написал её прототип так:

std::list<std::string> scanDir(const std::string &path)

Но стандартные контейнеры и строки по вышеописанным причинам в пролёте, а мне надо вернуть список.

Вижу два варианта:

1) пишем интерфейс «итератор по файлам», внутри либы пишем реализацию итератора, который хранит в себе std::list<std::string>, позволяет бегать по нему;

2) пишем интерфейс «коллбэк», передаём его в ф-цию, та его дергает, а мы на стороне клиента уже строим std::list<std::string>.

Покритикуйте пожалуйста подходы. Может, есть третий вариант?

★★

Может, есть третий вариант?

Пользоваться Си для подобных функций. Внутри можно с++, а интерфейс лучше Си. Соотно, для списков GLib или свой похожий лисапет.

pef-secure ()

Пимпл или сишный ифейс и никаких „третьих вариантов“. Кроме экзотических вроде корбы или уродского вебапи. Всё остальное чревато всякими неприятностями.

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

Пимпл не проканает из-за манглинга имён членов класса, поэтому вместо него и юзается связка «интерфейс+фабрика+уничтожалка»: из либы торчат только фабрика и уничтожалка, которые extern «C».

Что касается сишного интерфейса, то вы оба предлагаете запилить дополнительный сишный модуль, манипулирующий списками, получается. Но в таком варианте коллбэк, строящийся список на стороне клиента выигрышнее, на мой взгляд: требуется только написать код, последовательно строящий std::list, а функции манипуляции у него уже есть.

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

Угу а члены интерфейса не мэнглятся, да.

В общем случае, как это ни прискорбно крестовое апи пашет только в рамках одно конпилятора/поколения конпиляторов, ибо не стандартизированно.

Хотя, авторов кучи либ это не останавливает, ибо если есть руки^Wбилдсервера, то можно собирать под все мажорные платформы и не париться, а в замен иметь удобное апи, без костылей ради обхода бинарной несовместимости.

batbko ()

Итератор - много функций в интерфейсе. Коллбэк - много буков на клиенте, может ограничивать встраивание в собственный цикл обработки событий клиента.

Я бы выбирал итераторы и вообще всё что укладывается в концепцию хэндла - но это считай написания параллельно чисто сишного интерфейса. Но с другой стороны, это не так уж и плохо.

Вообще тот кто стартовал проект - большой косяк, он должен был подумать об этой проблемме заранее и выбрать правильный инструмент.

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

не проканает из-за манглинга имён
std::list<std::string> scanDir(const std::string &path)

Ну как знаешь.

nanoolinux ★★★★ ()

1) пишем интерфейс «итератор по файлам», внутри либы пишем реализацию итератора, который хранит в себе std::list<std::string>, позволяет бегать по нему;

а что случится, если пока один итератор «бегает», мы уничтожим какой-то эл-т в коллекции, например тот, на котором итератор? Получается, что коллекция должна иметь в себе коллекцию итераторов, что-бы при изменении коллекции как-то(как?) корректировать все итераторы. Либо при изменении коллекции колекция форкалась, и старый итератор бегал-бы по старой коллекции.

2) пишем интерфейс «коллбэк», передаём его в ф-цию, та его дергает, а мы на стороне клиента уже строим std::list<std::string>.

только учтите, что передавать нужно не указатель на функцию, а указатель на метод класса, который привязан к своему this, если он не статический. Тут тоже возникает вопрос: что случится, если этот this(и/или всё/что-то, что с ним связано) изменится в процессе обхода?

emulek ()

а почему не сериализовать объект std::list<std::string> например в тот же json? и на стороне клиента библиотеки уже построить по json обратно std::list<std::string>?

EugeneBas ★★ ()

Может, есть третий вариант?

Не использовать библиотеки, плюсы подходят только для полной сборки всего в один большой бинарь.

mashina ★★★★★ ()

пишем интерфейс «итератор по файлам»

this

annulen ★★★★★ ()

Можно запилить свой класс для строк и использовать его в API (так делает, например, ICU). Репутация std::string все равно подмочена различиями в реализациях, влияющих на скорость работы и потребление памяти (COW/не-COW, наличие/отсутсвие зарезервированного места для малых строк)

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

Ну как знаешь.

Не понял. Прототип приведён чтобы не расписывать долго, что функция делает. И так бы она была реализована, будь она не частью либы. Понятно, что в либе она принимает const char *, а о возвращаемых данных мы беседуем в данный момент.

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

а что случится, если пока один итератор «бегает», мы уничтожим какой-то эл-т в коллекции

Мысль такая: в функции строится полный std::list, но возвращается не он, а, грубо:

return new OwningListIterator(std::move(list))
То бишь итератор полностью владеет списком, который он обходит, и никому его не даёт менять.

этот this(и/или всё/что-то, что с ним связано) изменится в процессе обхода?

А здесь идея такая, что объект, содержащий коллбэк — это чисто построитель списка, временно создаваемый и отдающий построенный список. Делаем враппер для scanDir:

std::list<std::string> scanDirWrapper(path) {
  ListBuilder lb;
  scanDir(path, &lb); // построитель реализует известный ф-ции интерфейс
  return lb.GetList(); // здесь список мувается из построителя
}

uuwaan ★★ ()

собственная реализация списка на POD-ах пишется за пять минут.

template<class T>
struct ListElement{
   ListElement *prev, *next;
   T* data;
};


ListElement<char> path_list;


с обслуживающими функциями, думаю, разберётесь.

next_time ★★★★ ()

Вижу два варианта.
. . .
Может, есть третий вариант?

Да: предоставлять оба интерфейса. Очевидно, что кому-то может понадобиться один вариант, а кому-то ещё — другой.

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

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

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

ListElement *prev, *next;

обычно достаточно только *next, если не нужно назад идти. push/pop/append работают.

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

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

next_time ★★★★ ()

Пимпл не проканает из-за манглинга имён членов класса

lol whut. В кутэ канает а у тебя нет?

ну или так как-нибудь

/*где-то в начале заголовка*/
#define MYMAGIC_API	__stdcall

#ifndef _STATIC_MODEL_
#ifdef MYMAGIC_EXPORTS
#define MYMAGIC_CORE_API __declspec(dllexport)
#else
#define MYMAGIC_CORE_API __declspec(dllimport)
#endif
#else
#define MYMAGIC_CORE_API /*тут можем добавить и gcc магеи, но мне лень */
#endif

/*пишем свой класс без мангла*/
class MYMAGIC_CORE_API MyClass : public MySuperClass
{
   /*...*/
}

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

пишем свой класс без мангла

И тут тоже будет мангл. В большинстве случаев имена будут совпадать, конечно, но я обязан учесть ситуацию, когда либу собранную MSVC будут вызывать из клиента, собранного Борландовским компилятором. И тут имена не совпадут.

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

да тут ИМХО «общий случай» это как раз односвязаный список, потому что двусвязанный нужен олько если у тебя есть какой-то вектор указателей, нацеленный на список, и ты через него попадаешь куда-то в середину. Но для списков это не типично ИМХО(списки могут расти как угодно, а вектор/массив не может. Ну а если дерево/хеш навернуть, то зачем тогда список?)

ЗЫЖ это я так, для поддержания флейма.

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

Дык проверено и с борландом, все в порядке, просто нужно будет создать той же борландовской тулзой implib (вроде), подходящую борландовскому линкеру lib-ку. С dll не будет проблем.

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