Конференция "Основная" » Работа с плагинами [D7]
 
  • dreamse © (20.01.12 02:51) [0]
    Приветствую.

    В виде плагинов использую обычные DLL c набором функций:

    1) имя, версия и пр.
    2) функция старта (тут обычно в плагине запускается отдельный поток со своим функционалом)
    3) Функция передачи команды в плагин
    4) Функция получения данных из плагина

    Схема очень простая, примеров в сети кучи. Если нужно приведу листинг.

    Все работает, но возникли проблемы при расширении, т.е плагинов уже много и функционал растет.

    Проблема в том что если возникает критическая ошибка в одном из плагинов - падает все приложение (почему то вспомнил miranda которая падает через раз :) )

    Как можно защитить приложение от падения ? в идеале - переодически опрашивать плагины и выгружать те которые не отвечают (либо перезапускать)
  • Дмитрий Белькевич (20.01.12 14:14) [1]
    DLL - это dynamic linked library, после линковки дллка - это фактически кусок вашего экзешника. Если критическая ошибка возникает в дллке - считайте, что критическая ошибка возникает в куске экзешника.

    Что делать:

    1. Искать ошибки и их устранять.
    2. Выполнить плагины в виде отдельных процессов.
  • dreamse © (20.01.12 14:49) [2]
    1. Плагины пишутся так же сторонними людьми. Отловить все баги нереально в принципе.
    2. Идея хорошая, но как ее раализовать ? Интересует такой момент как обмен сообщениями между плагинами.

    Есть ли какой то способ глобально поймать ошибку в dll и либо заблокировать ее try except либо выгрузить dll в которой произошла ошибка ?
  • icWasya © (20.01.12 16:08) [3]
    поскольку у каждой DLL свой менеджер памяти и своя RTTI  и вследствии чего передавать объекты, в том числе и наследники TException между модулями настоятельно не рекомендуется, то прокатят только административные методы, типа а) внутри плагина не должно возбужнаться исключение; б) если таковые возникают, то перехватывать внутри функции плагина и превращать их во вменяемые код ошибки в) если это не выполняется, сильно бить по рукам разработчиков плагинов .

    Можно ещё посмотреть в сторону safecall
  • Дмитрий Белькевич (20.01.12 16:23) [4]
    1. Бить по руками и заставлять ловить блоками try.
    2. Смотреть в сторону IPC.
  • Dimka Maslov © (20.01.12 19:26) [5]

    > icWasya ©   (20.01.12 16:08) [3]


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


    >
    > Как можно защитить приложение от падения


    Код вызова функций из плагина обернуть в try..except. При словленном исключений - банить плагин навсегда. Впрочем от сильно глобального сбоя это может и не прокатить.
  • dreamse © (20.01.12 21:47) [6]
    Нашел баг который рушит всю систему.

    Это обращение к уже удаленному объекту (Free)

    если эта ошибка происходит в обычном приложении то появляется лишь диалог с ошибкой (access violation) и можно закрыв дальше работать.

    Если эта же ошибка возниает в плагине (dll) то она сразу выбивает и рушит все приложени.

    Вот что странно.
  • dreamse © (20.01.12 22:05) [7]
    В чем еще проблема.

    В DLL запускаются потоки.
    И если их основного приложения выгрузить DLL методом FreeLibrary все данные уничтожаются а потоки продолжают работать неокторое время, обращатся к уже уничтоженным данным и генерировать исключения.

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

    пробывал FreeLibraryAndExitThread

    http://www.vsokovikov.narod.ru/New_MSDN_API/DLL/fn_freelibraryandexitthread.htm

    Тоже самое, потоки продолжают работать.

    Есть ли решение по правельной выгрузки загруженной DLL ?
  • Inovet © (21.01.12 00:22) [8]
    > [7] dreamse ©   (20.01.12 22:05)
    > Есть ли решение по правельной выгрузки загруженной DLL ?

    Подать знак потокам и дождаться их завершения в функции ДЛЛ DllEntryPoint
  • Leonid Troyanovsky © (21.01.12 11:26) [9]

    > Inovet ©   (21.01.12 00:22) [8]

    > Подать знак потокам и дождаться их завершения в функции
    > ДЛЛ DllEntryPoint

    Нельзя там ожидать.

    http://msdn.microsoft.com/en-us/windows/hardware/gg487379.aspx

    --
    Regards, LVT.
  • Inovet © (21.01.12 13:01) [10]
    > [9] Leonid Troyanovsky ©   (21.01.12 11:26)
    > Нельзя там ожидать.

    Хм. Ну, если так, то тогда остаётся написать специальную функцию для остановки потоков, и вызывать её перед выгрузкой ДЛЛ.
  • Dimka Maslov © (21.01.12 14:28) [11]
    Ну надо было начинать с того, что там есть потоки. С потоками всё гораздо сложнее. Предлагаемое решение - каждый поток должен время от времени проверять общий флаг завершения и немедленно прекращать работу при его выставлении. Другое дело, что плагины может разрабатывать кто угодно... Как-то я разговаривал с разработчиками одной проги. У них была возможность подключения плагинов и расширения функционала третьми лицами. Потом они отказались. Почему? А потому-что вопросы пользователей по глакам плагинов адресовались им, не разработчикам плагинов. Когда это надоело, лавочка прикрылась.
  • dreamse © (21.01.12 17:23) [12]
    В общем проблема с глючным плагином решилась. Не без помощи данного форума должен заметить.

    В общем:

    Для разраотчиков плагинов нужно составить правила работы которым они должны следовать.

    1. Старатся по возможности не использовать в dll потоки
    2 Если использование потоков все таки необходимо то

    Отслеживать выгрузку DLL плагина:


    procedure DLLEntryPoint(dwReason: DWord);
    begin
     case dwReason of
       //DLL_PROCESS_ATTACH:
       DLL_PROCESS_DETACH:
         begin
           if assigned(dmMod) then
             begin
               TerminateThread ...
             end;
         end;
     end;
    end;

     DllProc := @DLLEntryPoint;
     DllEntryPoint(Dll_Process_Attach);




    где в DLL_PROCESS_DETACH: убивать все созданные потоки.

    3. Не использовать в коде функцию sleep. вообще!
    Если нужна задержка воспользоватся таймером (либо c TDataModule либо создать временный таймер - нужно создавать свое окно.)

    Проблема в SLEEP очень простая  - если пока идет задержка (в данном коде это было в компоненте расположенном на TDataModule) выгрузить библиотеку то после sleep будет выполнен дальше код - который обратится к уже уничтоженному объекту.

    -----

    В принципе текущая проблема решилась, осталось придумать как сделать глобальную обработку ошибок именно в DLL чтобы не падало все приложение из за кривых рук разработчиков.
  • dreamse © (21.01.12 17:26) [13]
    > Inovet ©   (21.01.12 13:01) [10]

    Лучше как показал в предыдущем примере. Так будет правельней отслеживать выгрузку.
  • Inovet © (21.01.12 17:39) [14]
    > [13] dreamse ©   (21.01.12 17:26)
    > Лучше как показал в предыдущем примере. Так будет правельней
    > отслеживать выгрузку.

    Я не говорил про "убивать потоки".
    Поток должен сам корректно завершиться. Для этого надо выставить флаг завершения, который поток периодически проверяет и завершается при его поднятии. т.е.
    TerminateThread
    принудительно завершит поток, а флаг только сообщит ему о необходимости завершения. Далее
    WaitForSingleObject
    но вот сказали, что нельзя ждать в
    DLLEntryPoint
  • Дмитрий Белькевич (21.01.12 20:25) [15]

    >  после sleep будет выполнен дальше код - который обратится
    > к уже уничтоженному объекту.


    Если объект уничтожен и обнилен - можно попробовать сделать проверку на Assigned.
  • Dimka Maslov © (21.01.12 21:21) [16]

    > В принципе текущая проблема решилась, осталось придумать
    > как сделать глобальную обработку ошибок именно в DLL чтобы
    > не падало все приложение из за кривых рук разработчиков.
    >


    Тут можно посоветовать разбить своё приложение на два отдельных процесса, один из которых управляет плагинами и тесно взаимодействует с основным. При падении процесса, ответственного за плагины, он возобновляется.
  • dreamse © (21.01.12 23:22) [17]
    >  При падении процесса, ответственного за плагины, он возобновляется.

    Если бы падение не сопровождалось еще выводом виндового сообщения о том что процесс некорректно завершился ....

    > Если объект уничтожен и обнилен - можно попробовать сделать проверку на Assigned.

    Да так и делаю - как видно из приведенного кода.
    Проблема в том была что на DataModule стоял компонент, например для работы с почтой у которого таймаут был по sleep. И обращеие происходило внутри самого компонента к себе же. Из за чего он вылетал. Решилось убиранием sleep.

    > Поток должен сам корректно завершиться.

    Логически это верно.
    Другое дело что если в этот момент попытатся завершить работу windows - винда сообщает что тот или иной процес не отвечает ( так как ждет когда же поток очнется) так что в этом случае удобней убивать поток.
  • Inovet © (21.01.12 23:37) [18]
    > [17] dreamse ©   (21.01.12 23:22)
    > Другое дело что если в этот момент попытатся завершить работу
    > windows - винда сообщает что тот или иной процес не отвечает
    > ( так как ждет когда же поток очнется) так что в этом случае
    > удобней убивать поток.

    Этот случай отдельно обрабатывать.
  • Inovet © (21.01.12 23:39) [19]
    Ты компьютер тоже отключаешь выдиранием вилки из розетки? Вот и потоки завершай штатно, а не прибиванием. Вроде это очевидно.
  • Дмитрий Белькевич (22.01.12 11:11) [20]

    > Другое дело что если в этот момент попытатся завершить работу
    > windows - винда сообщает что тот или иной процес не отвечает
    > ( так как ждет когда же поток очнется) так что в этом случае
    > удобней убивать поток.


    Sleep'ы лучше в потоках вообще не использовать, но уже сам убрал и хорошо - туда и дорога.
    Усыплять потоки удобнее TEvent'ами:


    FTerminateEvent: TEvent;




    procedure TDICOMStoreQueue.StartAndTerminate;
    begin
    ....
    FSession.Terminate;
    FTerminateEvent.SetEvent;




    constructor TSCUStoreSessionQueue.Create(AOwner: TDICOMStoreQueue);
    begin
    ...
    FTerminateEvent := TEvent.Create(nil, True, False, '');




    destructor TSCUStoreSessionQueue.Destroy;
    begin
    ...
    FreeAndNil(FTerminateEvent);



    Собственно, сама замена slee'а:


    FTerminateEvent.WaitFor(Owner.FTimeDelay);



    Так можно поток присыпить как sleep'ом, при этом можно его 'снаружи' запустить и остановить в любой момент перед закрытием приложения.
    Event'ов может быть несколько.


    >  ( так как ждет когда же поток очнется)


    Не нужно ожидать просыпания потоков, нужно их заставлять останавливаться.

    Глобально: разобраться с работой с потоками. Не всё там просто. Но - сделать можно. Сами на точно такие же грабли с потоками наступали:

    http://pda.delphimaster.net/?id=1302154341&n=0
 
Конференция "Основная" » Работа с плагинами [D7]
Есть новые Нет новых   [120378   +15][b:0.001][p:0.001]