Деструкторы не вызываются, когда собственное (C++) исключение распространяется на компонент CLR

У нас есть большой объем собственного кода C++, скомпилированный в DLL.

Затем у нас есть пара библиотек, содержащих прокси-код C++/CLI для обертывания интерфейсов C++.

Кроме того, у нас есть код C#, вызывающий оболочки C++/CLI.

Стандартные вещи, пока.

Но у нас есть много случаев, когда собственные исключения C++ могут распространяться в мире.Net, и мы полагаемся на способность.Net обернуть их в объекты System.Exception, и по большей части это работает нормально.

Однако мы обнаружили, что деструкторы объектов в области видимости в точке броска не вызываются при распространении исключения!

После некоторых исследований мы обнаружили, что это довольно известная проблема. Однако решения / обходные пути кажутся менее последовательными. Мы обнаружили, что если нативный код скомпилирован с /EHa вместо /EHsc, проблема исчезнет (по крайней мере, в нашем тестовом примере это произошло). Однако мы бы предпочли использовать /EHsc, поскольку сами переводим исключения SEH в исключения C++, и мы бы предпочли, чтобы компилятор имел больше возможностей для оптимизации.

Существуют ли другие обходные пути для этой проблемы - кроме обтекания каждого вызова через нативно управляемую границу в (нативный) try-catch-throw (в дополнение к уровню C++/CLI)?

4 ответа

Вы не делаете это правильно, я думаю. Использование _set_se_translator() уже требует компиляции с /EHa. Со страницы библиотеки MSDN:

You must use /EHa when using _set_se_translator.

А если серьезно, вы нарушите управляемый код, когда будете его использовать. CLR полагается на исключения SEH для обнаружения различных ошибок. Он использует SetUnhandledExceptionFilter, чтобы перехватить их. В частности, NullReferenceException и OverflowException (x86) возникают таким образом. Когда вы внедрите свой собственный блок __try, вы предотвратите попадание этого исключения в CLR. Хотя вы будете "обрабатывать" это, у вас не будет никаких следов точной причины исключения. И управляемые блоки try/catch не могут его обнаружить.

Лечение для эффективности / EHa (если это действительно проблема) - это 64-битный код. Его раскрутка стека на основе таблицы функций очень эффективна с нулевыми накладными расходами для блока try.

К сожалению, нет, я не верю, что есть хорошие обходные пути. Реализация исключений C++ на платформах MS реализована с использованием исключений SEH (IIRC). CLR подключается к обработке SEH, чтобы перехватить собственные исключения и обработать их в исключения CLR. Поскольку он ловит их на уровне SEH, исключение выглядит как исключение SEH для C++, и деструкторы запускаются или не запускаются соответствующим образом.

Итак, как вы заметили, два лучших варианта

  • Компилировать с /EHa
  • Добавить попытку / поймать в точке входа и выхода из ваших функций

В идеале, вы все равно должны заниматься вторым. По моему опыту считается плохой практикой позволять исключениям C++ пересекать границы компонентов.

Существует также вероятное хакерское решение, которое вы могли бы достичь, используя _set_seh_translator ( Документация). Однако я настоятельно рекомендую избегать этой функции, поскольку она может непреднамеренно нарушить обработку исключений CLR и вызвать много нежелательных проблем.

Страница MSDN для переключателя компилятора /E заявляет следующее:

http://msdn.microsoft.com/en-us/library/1deeycx5(VS.80).aspx

Вот соответствующая цитата:

Если вы используете /EHs, то ваше предложение catch не будет перехватывать асинхронные исключения. Кроме того, в Visual C++ 2005 все объекты в области действия, когда генерируется асинхронное исключение, не будут уничтожены, даже если обработано асинхронное исключение.

В основном /EHsc - это оптимистичный взгляд - он предполагает, что единственными исключениями являются настоящие исключения в стиле C++, и он будет оптимизирован соответствующим образом. /EHa, с другой стороны, принимает пессимистический взгляд и предполагает, что любая строка кода может вызвать генерацию исключения.

Если вы можете гарантировать, что никогда не станете причиной нарушения прав доступа, ошибки на странице или другого SEH, используйте /EHsc. Но если вы пишете услугу и / или хотите предоставить "лучшее из возможного", тогда /EHa будет необходимо.

Я также согласен с мнением @JaredPar о том, что исключения не должны выходить за границы модуля. То, что @nobugz говорит о том, как CLR обрабатывает исключения, может быть правдой, но я думаю, что есть разница между.Net-кодом, обращающимся напрямую к нативному коду с использованием P/Invoke, и обращением к C++/CLI-взаимодействиям DLL. В первом случае CLR должен справиться с ситуацией от вашего имени, в то время как во втором вы контролируете ситуацию и можете выполнить соответствующий перевод.

В версии 2.0 CLR есть ошибка, вызывающая эту проблему. Запуск вашего управляемого исполняемого файла в 4.0 CLR позволит вызывать деструкторы, как и ожидалось.

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

Другие вопросы по тегам