Реализация и соблюдение общего поведения ABI на С++ в UNIX системах
Недавно начал писать свою библиотеку логирования для дальнейшего его использования в будущих проектах на языке С++. Сама библиотека уже готова, но вот проблема! Оказывается, при написании библиотек, требуется соблюдать общее поведение ABI. Я этого не знал! Начал читать про саму концепцию ABI, и в принципе, я понял, что от меня требуется. Но вот незадача! В C++ ABI описываются подробности на двоичном уровне, которые не специфицируются в Стандарте. Стандарт описывает общее поведение, но не говорит как оно должно быть реализовано. При попытке найти что то сносное, в котором будут показаны примеры соблюдения ABI, я ничего не нашел. Есть ли наглядные примеры, в которых подробно показывают, как правильно реализовать поведение ABI?
Вот примеры, которые я так понимаю, являются частью реализации ABI:
// Использование поддержки динамической видимости символов ELF C++
#define DLL_PUBLIC __attribute__ ((visibility ("default")))
#define DLL_LOCAL __attribute__ ((visibility ("hidden" )))
// ...
class DLL_LOCAL Sink
{
public:
Sink(void) noexcept;
Sink &operator=(const Sink &) noexcept = default;
Sink &operator=(Sink &&) noexcept = default;
Sink(const Sink &) noexcept = default;
Sink(Sink &&) noexcept = default;
virtual ~Sink(void) noexcept = default;
// ...
virtual void print(const std::string &message) noexcept;
};
class DLL_PUBLIC StdoutSink : public Sink
{
public:
StdoutSink(void) noexcept;
StdoutSink &operator=(const StdoutSink &) noexcept = default;
StdoutSink &operator=(StdoutSink &&) noexcept = default;
StdoutSink(const StdoutSink &) noexcept = default;
StdoutSink(StdoutSink &&) noexcept = default;
~StdoutSink(void) noexcept override = default;
// ...
void print(const std::string &message) noexcept override;
};
А так же, и такой пример:
class StdoutSinkImpl
{
public:
StdoutSinkImpl(void) noexcept;
StdoutSinkImpl &operator=(const StdoutSinkImpl &) noexcept = default;
StdoutSinkImpl &operator=(StdoutSinkImpl &&) noexcept = default;
StdoutSinkImpl(const StdoutSinkImpl &) noexcept = default;
StdoutSinkImpl(StdoutSinkImpl &&) noexcept = default;
~StdoutSinkImpl(void) noexcept = default;
// ...
void print(const std::string &message) noexcept
{
std::cout << message << std::endl << std::flush;
}
};
class StdoutSink
{
public:
StdoutSink(void) noexcept : p_stdoutSinkImpl(new StdoutSinkImpl)
{
}
StdoutSink &operator=(const StdoutSink &) noexcept = default;
StdoutSink &operator=(StdoutSink &&) noexcept = default;
StdoutSink(const StdoutSink &) noexcept = default;
StdoutSink(StdoutSink &&) noexcept = default;
~StdoutSink(void) noexcept
{
if (!p_stdoutSinkImpl) {
delete p_stdoutSinkImpl;
}
}
// ...
void print(const std::string &message) noexcept
{
p_stdoutSinkImpl->print(message);
}
private:
StdoutSinkImpl *p_stdoutSinkImpl = nullptr;
};
Во втором случае, мы скрываем класс реализации, от класса, которым будем пользоваться, для того, чтобы если при модификации полей класса StdoutSinkImpl, класс StdoutSink был бы неизменен, и не подтвергался перекомпиляции, так как приватный объект его всегда равен размеру указателя. Данный способ, я так полагаю, примерно реализует поведение ABI, но все же, не уверен. Но на счет первого примера, у меня нет идей, как правильно его использовать. Если у Вас есть идеи и конкретные примеры реализации ABI, я буду Вам очень благодарен.
Ответы (1 шт):
У Вас нет необходимости реализовывать ABI, особенно, если это не системная библиотека. Его реализовали за вас в компиляторах. Действительные и простые изменения в ABI Вы можете вносить меняя декларацию вызова функций, идущих на экспорт, но, опять же, бинарный интерфейс, в конечном счете, за компилятором.
Если интересно больше почитать про ABI в UNIX, то вот https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html
И в UNIX нет dll, есть so :)