Pull to refresh

Comments 54

В опубликованной статье разъехался исходный код и получилось не аккуратно, хотя в предпросмотре все было нормально, может кто подскажет как исправить?
ля исходного кода есть тег <source lang=«cpp»></source>
Спасибо, разобрался, надо было убрать галочку «Отключить автоматические переносы строк и создание ссылок.»
Завтра буду смотреть, накипела подобная проблема для своего проекта, хотелось, что-то подобное делать, а тут раз и готово.
совершенно не понимаю зачем это всё.
> отсутствие возможности использовать пространства имен
это в каком месте отсутвует такая возможность? всю жизнь пользуюсь

> большое количество служебного кода, необходимого:
в каком месте? у меня всё использование заключается в инклюде хэдера и ликовки lib (delay loaded если требуется опциональное использование)

или хотелось изобрести что-то вроде COM?
>совершенно не понимаю зачем это всё
>> отсутствие возможности использовать пространства имен
> это в каком месте отсутвует такая возможность? всю жизнь пользуюсь
Предположим две DLL (DLL_A и DLL_B) экспортируют функции c одинаковыми именами encode. Поскольку экспорт функций в пространство имен не вложишь, приходится делать такое:
dll_a_encode в одной библиотеке;
dll_b_encode — в другой.

>> большое количество служебного кода, необходимого:
>в каком месте? у меня всё использование заключается в инклюде хэдера и ликовки lib (delay loaded если > требуется опциональное использование)
1) Для динамической линковки DLL необходим дополнительный код LoadLibrary, GetProcAddress и FreeLibrary ;)
2) Для гарантии отсутствия переходов исключений C++ через границы DLL, необходимо в экспортируемые функции вставлять try {...} catch (...) {} и реализовать свой механизм сигнализации об ошибки через коды возврата.
3) Экспорт классов напрямую не поддерживается, сколько кода необходимо написать для того, чтобы это реализовать, думаю, объяснять не стоит.
Расскажите пожалуйста, почему вам нужно загружать DLL динамически. Почему вам не подошла технология delay load?
Там написано, что проблемы две — механизм (1) глючит на платформе Alpha и (2) может не работать на компиляторах, отличных от дефолтного на платформе Windows.

Готов поверить что для некоторых задач это может быть критично. Значит ли это, что технология априори нехорошая?
Судя по словам автора, глюки на платформе Alpha — это один из нескольких примеров странного поведения. Жаль, что он не рассказал, каких. Так что никто никого не останавливает от использования этой технологии в проектах, для которых существующие ограничения не критичны.
В основном, для возможности использовать несколько DLL, имеющих один и тот же интерфейс, в приложениях, использующих так называемую «плагинную» схему.

Применительно к DLL, созданным с применением DL_LIBRARY и DL_EXPORT — это скрытие информации об экспортируемых функциях. Такие DLL в своей таблице экспорта содержат упоминание только об одной функции dynamic_library.
Т.е. мы говорим о модулях, написанных на разных компиляторах? Потому что если используется MS VC с одной версией CRT, то никаких проблем с вышенаписанным нет.

Тогда рассказывайте как гарантируется ABI конструкций:
DL_NS_BLOCK(( shapes )
(
    DL_LIBRARY(lib)
    (
        ( shapes::figure, create_rectangle, (double,left)(double,top)(double,width)(double,height) )


С виду экспортируется только
 extern"C" DLX_DLL_EXPORT ::dlx::library<dl_library>::ftable const* DLX_CALL dynamic_library()

Т.е. никакого C-интерфейса нет и кроме как через эту обёртку длл-ку не заиспользовать

И дальше хак на хаке:
    fn("_dynamic_library@0", lib_fn, std::nothrow);\
    if (lib_fn == 0)\
        fn("dynamic_library@0", lib_fn, std::nothrow);\
        if (lib_fn == 0)\
            ::dlx::throw_ex< ::dlx::runtime_error >( std::string("library '") + dll_path + "' is not a DL-library");\

как-то мне немного не по себе от такого )
DLL, собранные с использованием DL_LIBRARY и DL_EXPORT, всегда в таблице экспорта содержат только одну функцию (_dynamic_library@0 — MSVC и dynamic_library@0 — GCC). При проектировании DynLib одним из ключевых моментов было избавить пользователя библиотеки от лишних (и зачастую рутинных) действий при написании своей DLL, в частности убрать необходимость задавать def-файл. Два разных имени одной и той же функции — следствие реализации разных способов именования функций при экспорте для разных компиляторов без def-файла.

Все функции, которые описаны в DL_LIBRARY находятся в ::dlx::library<dl_library>::ftable.

> Т.е. никакого C-интерфейса нет и кроме как через эту обёртку длл-ку не заиспользовать

Как раз наоборот, все функции библиотеки и интерфейсов передаются через C-совместимые типы через структуры, напоминающие VTable в языке C++.
Но, действительно, подключение и использование таких DLL без DynLib потребует описания необходимых С-структур самостоятельно.
> Т.е. мы говорим о модулях, написанных на разных компиляторах?

Именно так (и только о компиляторах C++).
Присоединяюсь, возникли те же вопросы, и еще один.

Дает ли описанная в статье техника независимость от ABI (скажем использовать C++ библиотеку, которая компилировалась с помощью VS2010, в проекте, который собирается при помощи gcc)?
К независимости от ABI стремились: библиотеки, написанные на gcc, использовались в приложениях на VC. Обратное тоже, пробовали. Если что-то упустили, будем, по возможности, исправлять.
не поленитесь и допилите поддержку *nix
В никсах вроде как с C++ ABI все утрясено, более или менее — необходимость в такой библиотеке остро не стоит.
Но если сделать так, чтобы один и тот же код компилировался везде, есть очень хорошо
ПО для данных ОС почти не пишем. Но об этом подумаем.
Ссылки в тему: gtk+ (vala -> генерация кода на си), Qt, boost (пока не является частью boost).
Исправьте лицензию в шапке файлов и поместите LICENSE.txt, чтобы люди смогли использовать библиотеку в своих проектах. Галочки MIT в google code недостаточно.
Я думал, что размещение текста лицензии в заголовочных файлах будет достаточно. Разве не так?
Можете описать подробнее, как происходит заворачивание исключений?

Вот к примеру библиотека использует исключение my_exception: public std::runtime_error. Как при этом код, который вызывает эту функцию извне может понять, что там сидит тип my_exception и извлечь нужные параметры, например, которые сидят в исключении?
На данный момент используется упрощенная схема.
в каждой экспортируемой библиотекой функции и в каждом методе интерфейса вставляется следующий код:
try
{
    // прямой вызов функции объекта (библиотеки)
}
catch ( ... )
{
    return create_error_info();
}


Функция create_error_info определена следующим образом:
error_info* create_error_info()
{
    try
    {
        throw;
    }
    catch (std::exception const& e)
    {
        return create_info( typeid(e).name(), e.what() );
    }
    catch (...)
    {
        return create_info( "unknown", "not available" );
    }
}


Несложно определить, что для этого используется RTTI для получения имени класса исключения и строка, возвращаемая через метод what(). Это, конечно, не лучший способ общего решения (особенно для тех компиляторов, в которых typeid(...).name() возвращает нечитаемое имя класса).

Естественно, можно позволить пользователю библиотеки писать свою реализацию функции create_error_info через callback-функции или специализацию шаблонной функции (класса). И для этого, конечно, нужно будет пересмотреть состав структуры error_info, хранящую всю необходимую информацию об исключении.

Если это действительно необходимо, то, по возможности, будем улучшать DynLib.
О даа… код адов на вид! И вообще не понимаю, чем не нравится традиционная линковка? Работает на всех компиляторах, на всех осях, не имеет таких проблем с синтаксисом и вообще она быстрее происходит.
Ну а для загрузки плагинов приведённый метод тоже страшен.
Код необычен, это правда. Кто работал с BOOST.PREPROCESSOR поймет почему он такой. Кто не работал, тому он не покажется очень сложным. А кто не хочет — это уж его проблемы. Читайте второй абзац топика — мы делимся уже полученным результатом.

Страшно что? Количество кода или его внешний вид. Количество кода минимально, если у Вас есть варианты сделать тоже самое, но с меньшим количеством кода (без применения дополнительных преобразователей исходного текста, типа moc-компилятора в QT и пр.) приложите, пожалуйста, код. Внешний вид странноват, уже сказано. Но он читаем и понятен.

Основная идея создания данной библиотеки — возможность использования классов в межмодульном взаимодействии. Может в документации это и не отражено, но интерфейс DL_INTERFACE предназначен не только для экспорта классов из DLL, но и как общий интерфейс, который может реализовывать классы EXE и передавать их в DLL. (про плагины — это ответ на вопрос о динамической линковке, читайте внимательно).

Есть категория людей, которая:
а) не использует исключения при написании DLL (или использует, но не знает что, с ними нужно делать на границах DLL-EXE, DLL-DLL);
б) не использует классы в межмодульном взаимодействии;
в) для всего проекта всегда использует один и тот же компилятор, всегда с одними и теми же настройками, и, к тому же, всегда используется динамический рантайм.
г) не использует пространства имен — единственное средство С++, позволяющее хоть как-то получить подобие модулей в программе.

Для такой категории людей библиотека DynLib, действительно, не подходит, а добавляет только дополнительный «оверхед».
Почему не пошли по пути генерации кода оберток отдельным препроцессором?
Такой вариант рассматривался. И его проще реализовать. Но при таком варианте требуется как-то прикручивать кодогенератор (препроцессор) к среде (настраивать систему сборки). Это, конечно, не проблема, но хотелось чего-то, что будет работать в рамках возможностей языка C++.
Ну короче говоря для тех, кто в основном opensource проектами пользуется и все собирает одним компилятором да и исключениями в виду их некоторых проблем в винде не пользуется всё это особо и не нужно. Кстати, а какая проблема с пространствами имен то, спокойно ими пользуюсь и линкуюсь с библиотеками, которые их используют и гружу плагины с ними и проблем н е испытываю.
исключениями в виду их некоторых проблем в винде не пользуется
А какие в винде проблемы с исключениями?
>А какие в винде проблемы с исключениями?
С какими: SEH или исключения C++?

С SEH проблем нет, а вот с С++ есть (при межмодульном взаимодействии EXE-DLL, DLL-DLL):
1) у каждого компилятора может быть своя реализация механизма исключений;
2) типы данных (в данном случае внутренне представление классов исключений может быть по-разному определено);
3) даже, если компилятор один и внутреннее представление одно и то же, менеджер памяти может быть разным.
Я так понимаю, что проблемы с проброской исключений между DLL-DLL и EXE-DLL могут быть из-за того, что они могут быть собраны разными компиляторами и использовать разный рантайм. Я так понимаю, что под Linux, например, бросать исключение, пересекающее границу динамической библиотеки, в случае, если одна библиотека собрана GCC4, а другая GCC3, также опасно, или нет?
Часто ты видишь сейчас что-то собранное с gcc3 кроме древних проприетарных прог времен царя гороха? В Линуксах да и в прочих юниксах есть хоть какой-то стандарт де факто.
> Я так понимаю, что под Linux, например, бросать исключение, пересекающее границу динамической библиотеки, в случае, если одна библиотека собрана GCC4, а другая GCC3, также опасно, или нет?

Не работал под Linux. Не знаю. Но, имхо, правила применимости те же (возможно, есть еще что).
В Винде очень большие проблемы с исключениями, поэтому механизм SEH полностью и переписали для amd64. С++ же исключения обычно реализуются через SEH.

Генерация прерывания, переход в ядро, с раскручиванием SEH-фрэймов стэка вызовов, при этом, как правило останавливаются все конвееры процессора. Не говоря уже о локальной раскрутке, например при использовании try/finally, это нехилое замедление производительности.

Именно по этому, использование исключений кроме как для обработки ошибок, например для управления логикой программы, смертельно. Я их под IA32 вообще стараюсь не использовать. Кроме как мест, где без SEH не обойтись.
>Кстати, а какая проблема с пространствами имен то…
Разные компиляторы — разная декорация имен функций.
Весь нужный в деле opensource можно собрать одним компилятором а для мсовских либ можно и обертки сишные сделать.
Вы немного зациклены на opensource.
Я как-то привык использовать те инструменты во внутренностях которых я могу при надобности разобраться. И не поверите, но мне вполне хватает opensource библиотек для реализации весьма сложных проектов в том числе под винду.
Использовать не opensource я готов лишь в случае отсутствия вменяемой альтернативы.
Вы можете пользоваться чем угодно, но есть люди, которые с удовольствием пользуются и проприетарным софтом с закрытыми исходниками. И вот так получается иногда, что в процессе написания этого софта пользуются 3rd-party библиотеками также с закрытыми исходниками. И бывает, что библиотеки эти собраны не тем же самым компилятором, которым собирается всё остальное.

Ну а даже в случае с opensource, если, к примеру, библиотека собирается под Windows только MSVC, а вы её захотите собрать MinGW (или наоборот), то это может оказаться не так просто и быстро.
Проще дождаться clang'а с поддержкой MS ABI тогда уж и вообще не парить мозг на эту тему!
Было бы конечно хорошо, но судя по мейлинглисту ждать придется долго.
> а для мсовских либ можно и обертки сишные сделать
А можно воспользоваться DL_C_LIBRARY ;)
Посмотрим, если её не будет сильно сложно подключать. Пока проще 5ок методов сделать, чем с 3rdparty возиться.
Ну по порядку:

— Мне не нравится C++, большинство моих проектов на Си, даже если использую плюсы, то у меня это Си с классами, оборачивающие API.

— Я просмотрел мельком код и ничего не понял.

— Я абсолютно не понимаю, какое преимущество у этих макросов перед обычным объявлением dll-экспортируемых функций и обычной линковкой. Или использованием LoadLibrary/GetProcAddress. На первый взгляд кода не меньше. А при желании, можно написать враппер вокруг этого апи.

— Я не использую исключения в приложениях под ОС Windows, именно С++ исключения, а не чистый SEH или VEH.

Поэтому и страшно. Но на самом деле я просто ничего не понял в вашем коде, я уверен что я не один такой. А это очень плохо, если код не прозрачен. Не надо играться с чрезмерными абстракциями, на мой взгляд.
> Я просмотрел мельком код и ничего не понял.

Попрбуйте на практике, хотя бы повторить пример из статьи, и все встанет на свои места.
Если вы мы объясните, чем не устраивает LoadLibrary/GetProcAddress или линковка с либкой для динамической загрузки dll, то я повторю возможно.
1. Линковка с lib не получиться, если, например, lib создана в С++ Builder, а dll необходимо подключить в MSVC.

2. >Чем не устраивает LoadLibrary/GetProcAddress

Устраивает, но вот код с LoadLibrary/GetProcAddress

typedef void  (__stdcall  *init_fn)    ();
typedef void  (__stdcall  *decode_fn)  ();
typedef void  (__stdcall  *free_fn)    ();
 
int main()
{	
 
	HMODULE LibHandle = LoadLibraryA("decode.dll");
	if ( LibHandle == NULL )
	{
		std::cout << "error: Can't load library!";
		return -1;
	}
 
	init_fn init     = reinterpret_cast<init_fn>(::GetProcAddress(lib, "init"));
	decode_fn decode = reinterpret_cast<decode_fn>(::GetProcAddress(lib, "decode"));
	free_fn free     = reinterpret_cast<free_fn>(::GetProcAddress(lib, "free"));
 
	//Или другим способом определяем какая функция не загрузилась
	if (init == 0 || decode == 0 || free == 0) 
	{
		FreeLibrary(LibHandle); 
		std::cout << "error: Can't load function!"; 
		return -1;
	}
	
	init();
	decode();
	free();
	
	FreeLibrary(LibHandle);
 
	return 0;
}
 


А вот с использованием DynLib

#include <dl/include.hpp>
DL_BLOCK
(
	DL_C_LIBRARY( lib )
	(
		( void, __stdcall, (init),   () )
		( void, __stdcall, (decode), () )
		( void, __stdcall, (free),   () )
	)
)
 
int main()
{
	try
	{
		lib some("decode.dll"); 
		some.init();
		some.decode();
		some.free();
	}
	catch (std::exception const& e)
	{
		std::cout << "error: " << e.what();
	}
}
 


static double epsilon()

Возврат функцией объекта, превышающего размер регистра, нежелателен.
В данном случае, при нехватке места в eax (IA32), будет неявно задействован стэк, а в eax попадёт указатель на объект. Не смертельно, но не очень и приятно. Расширение стека потока может и обломиться например и тогда удивлённый программист получит исключение на ровном месте.

Лучше использовать указатель на объект для таких случаев, или ссылку.
Работу с числами с плавающей запятой, обеспечивает математический сопроцессор (FPU). FPU имеет специальные регистры, организованные в кольцевой стек. Вершиной стека является регистр ST(0). Возвращаемое значение функции epsilon() попадaет не в eax, а в ST(0) FPU.
Если Вы говорите о том, что экспортируемые функции библиотеки не должны содержать типы, размер которых превышает размер регистра, а передача объектов таких типов через стек по-разному реализуется разными компиляторами, то хочу Вас успокоить.

С-шный интерфейс библиотеки, реализованный через таблицы функций (псевдо VTable) реализован так, что функции в нем возвращают только указатель на экземпляр структуры ошибки:

Предположим, что библиотека экспортирует следующую функцию:
DL_LIBRARY( some_lib )
(
    ( double, sum, (double,x)(double,y,=0) )
)

В таблице экспортируемых функций используется функция sum_fn следующего вида:
struct some_lib_ftable
{
    error_info const* (*sum_fn) ( double x, double y, double& result );
    // служебные функции
};

Т.е. все в порядке с этой позиции.
Ошибся. Правильная версия: если экспортируемые функции библиотеки не должны ВОЗВРАЩАТЬ типы, размер которых превышает размер регистра.
Sign up to leave a comment.

Articles