Как стать автором
Обновить

Комментарии 14

Для экспортов, теоретически, есть UnmanagedExports, но у меня оно слёту не завелось, да необходимость и собирать 2 различных по битности варианта managed сборки (AnyCPU не поддерживается), меня оттолкнуло.

UnmanagedExports по ходу вообще нерабочий пакет, у меня тоже не заработал, разбираться в чужих багах не хотелось и в итоге делал свою реализацию в postbuild event. Две различных по битности managed сборки по моему не более отталкивающие чем 2 различных по битности unmanaged.
Да, с битностью, как ни крути, ни верти — 2 сборки где-нибудь да получаются. Интересно, если, экспортировать функцию из managed-сборки — вылезут ли те же самые грабли с подпиской на UnhandledException. Если не вылезут, это хороший плюс такому подходу и потенциальная работоспособность тулзы в консольном приложении.

Не увидел проверку очевидного варианта: написать загрузчик приложения, который инициализирует логи и передаст управление основной программе.


public static void Main(){
  InitLogs();
  YourApp.Program.Main();
}
Ваш вариант, несмотря на простоту, потенциально опасен тем, что кардинально меняет процесс запуска приложения.

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

Второе, те самые конфиги. Приложение будет пытаться подхватить не свой конфиг, а конфиг загрузчика. Решение (не уверен, что полноценное) — заменить конфиг загрузчика копией конфига приложения.

Третье — точки входа в приложение часто помечаются атрибутами, задающими COM threading model, например STAThread. «Решение» — писать по отдельному загрузчику на каждую threading model. Какой из них использовать, выбирать или вручную, или писать ещё один, головной загрузчик.

Четвёртое. Если приложение где-то в своих потрохах использует Assembly.GetEntryAssembly, то его может ждать большой сюрприз.

Будут ещё технические моменты по правильному определению точки входа приложения (а они разные бывают, взять хотя бы приложение на VB). По запуску приложения через reflection, передаче ему командной строки — но это всё решаемо в рабочем порядке.

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

PS:
Есть хорошая статья, описывающая, что делает CLR, перед тем как стартовать приложение. Не так уж и мало мест, где что-то ещё может пойти не совсем так, как ожидалось.
Есть же Thread.SetApartmentState, с его помощью можно установить правильную COM threading model выдернув ее через рефлексию.

А замена конфига загрузчика конфигом приложения заодно решит проблему правильного рантайма :-)

Кстати, можно создать новый AppDomain и запустить сборку в нем. В нем можно заменить главный конфиг и входную сборку (а вот версию рантайма придется копировать в конфиг загрузчика).
Выглядит не так уж страшно, и вроде как обещает даже сделать вызов в AppDomain-е по умолчанию. Непонятно, правда, в каком thread, но и на том спасибо.

А в каком потоке кроме текущего может выполняться блокирующий вызов?


Кстати, с этим могут быть проблемы: текущий же поток в DllMain висит, а там не все winapi разрешено использовать. Надо не забыть про трюк с APC на свой же поток, в котором уже делать основную работу.


Кстати, нет ничего сложного и в том чтобы в другом процессе на лету написать новую функцию. Это снимет ограничение в 1 строковый параметр для передачи.

По поводу поведения AppDomain.UnhandledException: так исключение-то не является Unhandled! Оно перехватывается и обрабатывается в цикле обработки сообщений.


Чтобы его перехватить, надо подписываться на Application.ThreadException (что и делает библиотека). Но это событие — локальное для потока!


Так что идея перейти в основной поток перед инициализацией библиотеки была правильная. Только надо не забыть обработать случай безоконного приложения (в таком случае надо инициализировать библиотеку в текущем потоке), а также WPF приложения (у WPF своя реализация цикла обработки сообщений).

А почему нельзя было снять дамп приложения при ошибке (либо стандартным taskmanager либо procdump либо настроить инструментами от MS dump по условию) а потом в теплой ламповой обстановке открыть его либо Ultimate VS 2013 (начиная с этой версии в линейке ultimate есть heapView для управляемых приложений — оч классная штука) либо уже захардкориться windbg? Это я про аварийные случаи (например ну кончилась память а локально ну никак не воспризвести и вроде бы всеж правильно сделано то), ибо ловить непойманные исключения и слезно просить их отослать разработчикам должно быть вроде как штатным функционалом любой сложной системы…
Насчёт штатного функционала сложной системы — эх, если бы всё было так радужно, не было бы у службы поддержки проблем.

Наша ситуаций такова. Мы разрабатываем компоненты, продаём их нашим клиентам, программистам. Те, в свою очередь, пишут с их использованием софт для своих заказчиков. Далее, довольно типовая тупиковая ситуация. Из 50 заказчиков у одного софт подпадывает. Заказчик жалуется своему поставщику, тот в свою очередь жалуется нам в саппорт. При этом проблема не поспроизводится нигде, кроме как на машине у того самого заказчика. Teamviewer к машине не дают, студию и прочие нужные тулзы поставить тоже нельзя, передеплоить новую версию приложения или нельзя вообще или не получается сделать быстро, а разобраться и пофиксить надо уже вчера.

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

Предлагаемые вами сбор дампов руками заказчика и прочие манипуляции, которые надо провести его силами, вполне применимы и возможны, но обычно уходит довольно много времени, чтобы объяснить заказчику, что и как ему делать. А наша служба подддержки тут в лучшем случае 3я в цепочке заказчик <-> программист <-> devexpress.
Ну если ВАША служба поддержки занимается ТАКИМИ ситуациями (у клиента клиента что-то не работает почините ибо это вы виноваты а мы даже callstack достать не можем), то во-первых мое Вам уважение (я сталкивался с дугими службами поддержки), а во-вторых — я понял к чему эта картинка в начале :)

Может следует добавить возможность логироания в компоненты, активируемую например параметром в конфиг файле, или фактом наличия файла или ключем в реестре уж если совсем все плохо и самим создавать нерадивым клиентам дампы :)…
Бывают именно такие ситуации, не каждый день, конечно. Когда к нам приходят с обоснованными подозрениями, что виноваты наши компоненты (например, перекомпилили с новой их версией и стало ломаться; но не воспроизводится ни у автора программы, ни у нас), нам по-любому надо докопаться до причины и уже править или на нашей стороне, и/или объяснить автору, как поправить на его стороне.

Иногда просто пытаются перекинуть свои проблемы на нас (особенно этим индусы грешат), и даже в этом случае стараемся сначала добыть объективную информацию, и на её основании уже вежливо указать клиенту, где он неправ.

Логировать было бы неплохо, но тут надо волевое политическое решение принимать, на уровне фирмы.
И приучать своих клиентов (особенно тех кто именует себя программистами) писать в тех. поддержку с вложением тестового проекта где ошибка сразу воспроизводится. Ибо весь мой опыт говорит что если ошибка есть в приложении но ее нет в тестовом проекте (пустой проект где где есть компонент и минимум логики) то вероятность проблемы в компоненте равна вероятности ошибки в компиляторе и практически нулевая…
Приучать — вообще бесполезняк, грамотные люди и сами проект прилагают, у остальных приходится его выцарапывать. Мне больше всего нравится подход, когда на заявление «у меня падает», мы сами отдаём клиенту работающий тестовый проект с комментарем в духе «вот это у нас работает, попробуй у себя и, если не валится, добавь код, чтобы упало». Очень часто после такого ответа человек самостоятельно находит и фиксит проблему в своём коде. И это всё за один раунд переписки.

А больше всего радуют (но нечасто) реально продвинутые ребята, которые приходят с проблемой типа «в методе X класса Y у вас в N-ной строке косяк, надо исправить вот так. поправьте плиз, чтобы мне из исходников ваши компоненты не собирать». Или, «я хочу что-то этакое закастомизить, почти всё сделал, но не хватает virtual в методe X класса Y, чтобы перекрыть и получить искомое. впишите, не сочтите за труд.»
Зарегистрируйтесь на Хабре, чтобы оставить комментарий