Не так давно мутил я на дотнете воспроизведение трекерных файлов, а именно, XM. Громоздкие либы типа BASS явно для этой задачи не подходили, широко известный в узких кругах ufmod почему-то в связке с дотнет интеропом не заработал, зато нашлась библиотечка minifmod, которая обладала всеми необходимыми качествами (интересовала возможность загрузки из файла и из памяти) и небольшими габаритами. Для того, чтобы можно было удобно указывать способ загрузки (из файла или из памяти), я дописал маленький адаптер, который обрабатывал callback’и минифмода, и засунул его в DLL. Соответственно, нативная DLL к .net-проекту была подцеплена через простейший interop.
Сегодня наконец собрался с мыслями, почистил файлы проекта, и выложил все на гуглокод, благо sourceforge и codeplex уже попробовал. Google code очень понравился своей беспроблемностью в залитии файлов и работе с SVN. Вспоминая, как я трахался с sourceforge через SFTP чтобы залить туда пару архивов, могу сказать, что здесь все очень и очень удобно.
Итак, сайт проекта находится здесь http://code.google.com/p/minifmod4net/
Код использования библиотеки прост как пять копеек, достаточно взглянуть на тестовый пример, чтобы все стало ясно. Только один момент – если вы желаете использовать ее в своей программе, то вам придется сменить режим генерации кода с AnyCPU на x86, поскольку имеется нативная DLL с 32-битным кодом, и в режиме AnyCPU на какой-нибудь 64-битной винде программа не запустится, мотивировав отказ исключением наподобие ImageBadFormatException. Если же х86 специфицировать явно, то все будет работать и на 64-битных Windows.
PS. Поздравляю всех с наступившим 2010 годом, надеюсь, он принесет всем нам немало радости и профессиональных успехов 🙂
Часто приходится писать обработчики исключений, которые делают одно и то же. Например, в следующем кусочке кода нам необходимо среагировать на исключения типа Exception1 и Exception2 записью в лог-файл :
try { // Блок, который может вызвать исключения Exception1, Exception2 // или исключение любого другого типа } catch (Exception1 exc) { logger.WarnException("An exception has been occured : {0}", exc); } catch (Exception2 exc) { logger.WarnException("An exception has been occured : {0}", exc); } |
Проблема в том, что мы не можем никак избежать дублирования кода. Единственный выход – создать отдельный метод-обработчик, в котором и инкапсулировать логику. Но – во-первых, это может оказаться неудобным, поскольку локальные переменные, которые могут понадобиться в обработчике, придется передавать при вызове функции, а во-вторых, сам по себе вызов функции – это еще несколько тактов процессора.
Хотелось бы иметь способ, который бы позволял писать, скажем, следующим образом :
try { // } catch (Exception1, Exception2 exc as Exception) { // exc имеет тип Exception } |
Не так давно я прикрутил фичу, описанную в предыдущем посте, к своим умным указателям, и вот недавно счастье было разрушено падением программы. Проблема была локализована в следующем коде :
const HandlePointer<lib3dsfile>& file = loadFile(); |
Функция loadFile() возвращала HandlePointer<Lib3dsFile> по значению, и по идее это значение должно было быть привязано (bind) к константной ссылке. Время жизни временного (temporary) объекта в этом случае по Стандарту должно быть продлено и временный объект должен быть жив до тех пор, пока жива константная ссылка. Однако, добавив диагностических логов, я с удивлением отметил, что деструктор временного объекта в данном случае вызывается сразу после выполнения этой строки, не дожидаясь выхода ссылки из области видимости. Замена ссылки на объект-копию магическим образом исцеляло программу, временный объект уничтожался после вызова конструктора копий, и программа работала как раньше.
Добавив еще чуть больше логов, я удивился еще раз, увидев, что в указанном выражении вызывается
оператор преобразования HandlePointer<T>::operator T& () , то есть объект преобразовывался к самому себе, однако при этом объект уже не привязывался к константной ссылке, а уничтожался автоматически после вычисления полного выражения (full expression).
Дома для проверки написал тестовую программу, которая объявляла класс Test и оператор преобразования к типам Test& и const Test& :
class Test { public: Test() { printf("Default constructor\r\n"); } ~Test() { printf("Destructor\r\n"); } Test(const Test& copy) { printf("Copy constructor\r\n"); } Test& operator= (const Test& copy) { printf("operator =\r\n"); return *this; } // operator Test& () { printf("operator Test&\r\n"); return *this; } operator const Test& () const { printf("operator const Test&\r\n"); return *this; } } Test someFunc() { return Test(); } int main() { { printf("Begin\r\n"); const Test& test = someFunc(); printf("End\r\n"); } } |
Натравив на него компиляторы Visual C++ и GCC, посмотрел на результаты. Произошло все так же, как и в случае шаблонных классов :
Вывод программы, скомпилированной Visual C++ :
Begin
Default constructor
End
Destructor
Вывод программы, скомпилированной GCC :
Begin
Default constructor
operator Test&
Copy constructor
Destructor
operator Test&
Destructor
End
На обилие конструкторов копий и деструкторов в выводе GCC можно не обращать внимания – это
всего лишь говорит о том, что GCC не выполнил здесь RVO (Return Values Optimization). Главное –
что в Visual C++ временный объект живет до конца блока { }, а в GCC уничтожается сразу.
Почему поведение GCC здесь мне представляется неверным ? Допустим, у вас есть класс. У класса есть
дефолтные операторы преобразования их к ссылке и к константной ссылке, генерируемые компилятором автоматически. И временные объекты успешно привязываются к константным ссылкам, продлевая их время жизни. Однако, если вы вдруг (как сейчас я) захотите написать свою версию такого оператора (что в принципе, бессмысленно, но в контексте использования шаблонов смысл приобретает), то вы уже не сможете действовать по старым правилам. Таким образом, явное определение пользовательского оператора преобразования (такого же, как и дефолтный, по сути) изменяет поведение объектов и семантику в целом.
В общем, сбило порядочно меня все это с толку, и я обратился за помощью на блог Алены С++ (в котором как раз недавно читал про RVO и привязку временных объектов к const& ) и на RSDN.
На следующий день поступили комментарии, позволяющие разобраться в том, что же все-таки происходит и какой из компиляторов в данном случае прав.
Краткое резюме беседы (для тех, кому лениво читать обсуждение) :
Здесь мы видим следствие изначального внутреннего противоречия в Стандарте 98 (см ветку на RSDN).
Стандарт был скорректирован в дальнейшем в ходе внесения правок и уточнений.
На данный момент корректным поведением является поведение, при котором компилятор не должен вызвать переопределенные операторы преобразования в случае, если это преобразование выполняется к ссылке на тот же тип или к объекту того же типа:
12.3.2/1
…
A conversion function is never used to convert a (possibly cv-qualified)
object to the (possibly cv-qualified) same object type (or a reference to it)
Таким образом, компилятор GCC на данный момент не соответствует исправленному Стандарту.
Что ж, печально, придется использовать вместо неявных преобразований явный вызов функции.
Хотя, возможно, есть способ, который бы позволил ограничить шаблонный метод преобразования таким образом, чтобы он не использовался, когда тип T идентичен типу U ? Я попробовал пошаманить с
шаблонами и SFINAE, но пока ничего путного не вышло. Если у кого-нибудь есть такое решение,
было бы интересно о нем узнать.
0