Delphi zdarzenie raise

0

Witam,

Dziś mam takie pytanie. Chociaż jestem dawno wiem, że wszelkiego rodzaju "raise" najlepiej jest obsłużyć poprzez try.... except end, to czasami mam do czynienia z bibliotekami zewnętrznymi, które również mają zdarzenie "raise" i tego poprzez try except nie da się przechwycić. A jeżeli się mylę to proszę mnie poprawić.
Ja chcę osiągnąć taki efekt - przechwycić zdarzenie raise - i schować to dla swojej wiedzy i nie pokazywać komunikatu w okienku (jakimkolwiek).

Pozdrawiam
Andrzej.

3

Powinno się dać chwycić - być może biblioteka rzuca np. ExceptionA, a Ty masz catch na ExceptionB?

Robiąc Except on Exception do powinieneś być w stanie wychwycić wszystkie możliwe wyjątki.

2
Andrzej Boczko napisał(a):

Witam,

Dziś mam takie pytanie. Chociaż jestem dawno wiem, że wszelkiego rodzaju "raise" najlepiej jest obsłużyć poprzez try.... except end, to czasami mam do czynienia z bibliotekami zewnętrznymi, które również mają zdarzenie "raise" i tego poprzez try except nie da się przechwycić. A jeżeli się mylę to proszę mnie poprawić.

Poprawiam Cię, nie masz racji.
Co do zasady możesz przechwycić dowolny wyjątek i go obsłużyć, poza Access Violation.

Problemem jest to, że mało kto potrafi poprawnie zarządzać wyjątkami.
I nie tylko w Delphi - programiści... 😁

Ja chcę osiągnąć taki efekt - przechwycić zdarzenie raise - i schować to dla swojej wiedzy i nie pokazywać komunikatu w okienku (jakimkolwiek).

To Ci nie pomogę, ponieważ to jest jedna z największych głupot i do tego ręki nie przyłożę.
Zdobądź książkę "Coding in Delphi" Nicka Hodges'a - pierwszy rozdział traktuje o wyjątkach - spis treści:

1.3. Jak nie korzystać z wyjątków (18)
 Nie "połykaj" wyjątków (18)
 Nie przechwytuj wyjątków bezkrytycznie (18)
 Nie nadużywaj wyjątków (19)
 Nie używaj wyjątków jako podstawowego sposobu sygnalizacyjnego (19)

https://helion.pl/ksiazki/programowanie-w-jezyku-delphi-nick-hodges,prodel.htm#format/e

0

Tu mi nie chodzi o standardowe metody. Patrząc w logikę aplikacji: każdy blok:

try
...
except
... 
end;

ma przechwycenie,
ale gdy jest mowa o takich sytuacjach

try
...
call from dll_lib_procedure
...
except
...
end; 

A wewnątrz dll_lib_procedure jest wyjątek raise - to nie zawsze to chwyci. Access Violation to jest błąd krytyczny tego nie da się przechwycić jeżeli "punkt" sięga do pustego bloku obiektu,...

1
wloochacz napisał(a):

Co do zasady możesz przechwycić dowolny wyjątek i go obsłużyć, poza Access Violation.

Jak to nie da się złapać Access Violation?

Chyba że w Delphi się nie da, ale dla FPC to taki sam wyjątek jak każdy inny — klasa EAccessViolation, komunikat Access violation. Można go normalnie przechwycić i obsłużyć, można go też olać (zjeść) i utrzymać przepływ sterowania bez zmian. Choć zjadanie wyjątków to raczej nie jest dobry pomysł.

Jeśli czegoś nie wiem to napisz coś więcej.


Andrzej Boczko napisał(a):

A wewnątrz dll_lib_procedure jest wyjątek raise - to nie zawsze to chwyci. Access Violation to jest błąd krytyczny tego nie da się przechwycić jeżeli "punkt" sięga do pustego bloku obiektu,...

Być może w przypadku DLL jest inaczej, być może w Delphi jest inaczej niż we Free Pascalu (choć wątpię), ale wyjątek EAccessViolation, jeśli jest generowany w przypadku pustej (nilowanej) referencji obiektu, może być i powinien być obsługiwany w sposób standardowy. Sprawdziłem taki przykład:

var
  List: TStringList = nil;
begin
  try
    List.Add('foo');
  except
    on E: Exception do
      Write('"', E.ClassName, '", "',  E.Message, '"');
  end;

Działa to wedle oczekiwań, na wyjściu dostaję to:

"EAccessViolation", "Access violation"

Ale znów – no chyba że Delphi działa inaczej lub kod wołany z DLL działa w inny sposób od nieimportowanego (jeśli o mechanizm wyjątków chodzi). W każdym razie, dorzuć sobie jakięs logowanie, tak aby widzieć jakie wyjątki są generowane i jakie otrzymywane są w bloku try except, tak aby się upewnić, że one faktycznie są rzucane i że (jak pisałeś) czasami nie są poprawnie obsługiwane.

0

Łapanie exception przy pomocy sprawdzenia klasy może się nie udać jeśli exception pochodzi z DLLki (osobny runtime).
Dotyczy to zarówno C++ jak i Delphi.
https://www.ostack.cn/?qa=1081020/

0

Ciekawe czy to chodzi o wszystkie wyjątki, czy tylko te, które nie znajdują się w runtimie (jakieś własne).
Nowa wiedza — fajnie. ;)

1
furious programming napisał(a):

Ciekawe czy to chodzi o wszystkie wyjątki, czy tylko te, które nie znajdują się w runtimie (jakieś własne).

Z tego co pamiętam w Delphi masz kilka rodzajów wyjątków:
a) natywne - zwykła obsługa
b) systemowe SEH - nad tym odstawia się chyba jakieś gusła
c) z innych runtime'ów - np. z C++, FPC czy Pythona w DLL
d) AV - też mi się kojarzy że jest nieprzechwytywalne w sposób normalny (może na sposób SEH), ale narzędzia typu EurekaLog powinny dać radę

0

@Andrzej Boczko:

Access Violation to jest błąd krytyczny tego nie da się przechwycić jeżeli "punkt" sięga do pustego bloku obiektu,...

@wloochacz

Co do zasady możesz przechwycić dowolny wyjątek i go obsłużyć, poza Access Violation.

Nie wiem skąd taka teza ?
Access Violation w delphi również da się przechwycić

@furious programming
Twój przykład w Delphi podobnie jak FPC przechwytuje wyjątek

0

Hm. Wszyscy macie swoją rację. Ja nie twierdzę, że nie da się (chociaż same Access Violations), zwykle nie miałem - jak miałem - to wiedziałem, że coś nie tak z lokalizacją obiektów (albo kompilacją) i zwykle po poprawkach ten problem znikał. Mam jeden przypadek gdzie pomimo, iż jest w kodzie (delphi), w bibliotece - komenda raise i to jest przechwycone (nieaktywne). Inne miejsce tego samego systemu "reaguje" na wystąpienie raise. Pisząc na grupie myślałem, że pomimo iż w delphi piszę od ponad 20lat, to jednak czegoś nie wiem. Nie ma ludzi doskonałych.

Natomiast sprawa wygląda następująco:

Dowolny wyjątek, prawie zawsze da się przechwycić i ja to wiem. Są drobne wyjątki problemów gdzie tego nie da się zrobić ale każdy kto programuje w Delphi wie o co chodzi.
a same użycie sekwencji :

try
except
end

powoduje ominięcie pewnego bloku wykonania, odpowiedzialnego za pewną grupę czynności. Ominięcie - czyli niewykonanie. A tu nie o to chodzi. Chodzi o dezaktywację "polecenia RAISE".

Na początku swojego wątku wyraźnie zaznaczyłem, że wiem o tej sekwencji try ... except end i jaki chcę osiągnąć efekt. Sama biblioteka o której wspomniałem ma się dobrze i nie jest dla mnie nieznana. Wiem co w niej siedzi, i to co tam zawiera polecenie "raise" jest potrzebne w każdym innym przypadku (dlatego nie chcę zmieniać biblioteki). Chcę tylko wprowadzić jeszcze jeden punkt (opracowanie jest zespołowe), wyjątek - odstępstwo od uruchomienia polecenia RAISE.

NImniejszym chyba można uznać, że temat nie ma rozwiazania - i muszę szukać dalej...

Dziękuję za pomoc i pozdrawiam serdecznie.

0

Zawsze możesz sobie dodać do tej bilbioteki zmienną, która będzie określać to czy wyjątki mają być produkowane i ”uzewnętrzniane” za pomocą raise. Stan tej zmiennej możesz modyfikować za pomocą dodatkowej funkcji w bibliotece, np. EnableExceptions, podając jej stan aktywności (jako wartość logiczna). Natomiast w kodzie biblioteki, zamiast puszczać wyjątek za pomocą raise, wykonaj warunek sprawdzający wartość zmiennej globalnej — jeśli jest pozwolenie na wypuszczenie wyjątku to wołaj raise, a jak nie to nie.

To najprostsze co mi do głowy przychodzi — łatwo dodać taki kod, nie psując obecnej postaci. Natomiast podczas wywołania głównej funkcji (tej, która ma nie rzucać wyjątków), najpierw wywołaj funkcję blokującą wydostawanie się wyjątków, następnie swoją główną, a na koniec tę odblokowującą wyjątki z powrotem. Cały pozostały kod pozostanie bez zmian i będzie działał tak samo.

To tylko ogólny zamysł — nie testowałem samodzielnie, ale powinien spełniać zadanie.

0

Jak rozumiem zarówno DLL jak i EXE, który z tej DLL korzysta to Twój kod napisany w Delphi i RISE z DLL obsłużony w try except w EXE

try
  funkcja_z_dll_ktora_rzuca_rise;
except
  on e: exception do
     write(e.message);
end;

powoduje, że mimo wszystko na ekran leci wyjątek zamiast go wyciszyć i przechwycić w except tak?

0

poniekąd tak, ale u mnie nie sekcji on e: exception do .... tam jest raise. Ogólnie poradziłem sobie wstawiając w którymś momencie przechwycenie wyjądku w wewnętrzne rozwiązanie obsługi błędów i "wyciszeniu" komunikatu w przypadku wystąpienia określonego kodu błędu = zakończony sekwencją "abort"...

2

DLL nie powinno w ogóle rzucać wyjatków na zewnątrz, bo to jest mocno problematyczne. Kod w DLL może wygenerowac wyjątek, bo np. korzysta z jakiejś klasy, która wewnętrznie te wyjątki tworzy — np. TFileStream czy cokolwiek innego. Albo po prostu leci EAccessViolation czy EStackOverflow czy tam cokolwiek innego z RTL.

Zrobiłbym więc tak, że funkcja w DLL wykonywała by swój kod w sekcji try except (żeby wyjątek nigdy nie wydostawał się z tej funkcji) i nie używałbym raise do puszczania go dalej. Zamiast tego, w sekcji except bym wyjątek łapał, rozpoznawał typ i przepisywał z niego informacje o błędzie do lokalnych zmiennych (po stronie DLL), a jedyne co by było zwracane przez funkcję to kod błędu (gdzie kod 0 oznacza brak błędu).

// po stronie DLL

var
  ErrorMessage: String;

function GetLastDLLError(): PChar; stdcall;
begin
  Result := PChar(ErrorMessage);
end;

function DoSomething(): Integer; stdcall;
begin
  Result := 0;

  try
    // kod mogący rzucić wyjątek
  except
    // tutaj selektywna obsługa wyjątów — poniższe tylko do zobrazowania działania
    on Error: Exception do
    begin
      ErrorMessage := Error.ToString();
      Result := 1; // kod zależny od klasy wyjątku
    end;
  end;
end;

exports
  GetLastDLLError,
  DoSomething;

end.

Aby po stronie aplikacji wiedzieć co się stało, dodałbym sobie w DLL funkcję (lub kilka) pokroju GetLastDLLError, do pobierania informacji o zainstniałym błędzie (w tym komunikat błędu, pozyskany z Exception.Message). Te informacje można by wykorzystać po stronie aplikacji — jeśli funkcja z DLL zwróciła niezerowy kod błędu (czyli błąd faktycznie wystąpił), to rzuciłbym wyjątek za pomocą raise, uzupełniając go w informacje pozyskane za pomocą wspomnianej funkcji GetLastDLLError.

// po stronie aplikacji

function GetLastDLLError(): PChar; stdcall external 'foo.dll';
function DoSomething(): Integer; stdcall external 'foo.dll';


// wrapper na funkcję z DLL

procedure DoSomethingWrapper();
begin
  if DoSomething() <> 0 then
    raise Exception.Create(GetLastDLLError());
end;

W ten sposób wyjątki nadal byłyby obsługiwane, zarówno po stronie DLL jak i aplikacji, jednak różnica polegałaby na tym, że wyjątki nie migrowałyby pomiędzy DLL a aplikacją, bo to jest problematyczne i raz działa, a raz nie. Ma to prawo działać wedle oczekiwań — trzeba tylko sprawdzić czy tak faktycznie jest.

0

Wspominając o wewnętrznym rozwiązaniu obsługi błędów właśnie takie podejście miałem na myśli ;)

1 użytkowników online, w tym zalogowanych: 0, gości: 1