Tekst informacyjny o polityce Cookies Close   
    
 
         
• 1. Strona główna
• 2. Kontakt: e-Mail
 
         
• 1. Baza danych
• 2. Tabele i kwerendy
• 3. Formularze
• 4. Raporty
• 5. Moduły i makra
• 6. Obsługa błędów
 
    

II.   VBA 

    
• 1. Okna Accessa
• 2. Okna Formularzy
• 3.Okna Dialogowe
• 4. Tekst
• 5. Liczby
• 6. Pliki
• 7. Inne
 
    

III.   API 

    
• 1. Ogólnie o API
• 2. Funkcje API
• 3. System
• 4. Praca z oknami
• 5. Okna dialogowe
• 6. API - Inne
 
         
• 1. Bitmapy 24 Bit
• 2. GDI i HDC
• 3. Kody kreskowe
• 4. Formant Image
• 5. FreeImage.dll
 
    

V.   Inne 

    
• 1. Shell i MsDOS
• 2. Kontrolki
• 3. ६ԼҚ ਸ
• 4. Unikod

 
Odwiedzin:

Logo AccessFAQ• I.6. Access - Obsługa błędów •

6.1. Błąd i jego obsługa.
6.2. Obsługa błędów na pl.comp.bazy-danych.msaccess.
6.3. Błąd w MS Access - jak pobrać numery błędów i ich opisy ?
6.4. Jak pobrać opis błędu wywołanego podczas uruchamiania funkcji API, na przykładzie błędnych argumentów wywołania funkcji ShellExecute (...) ?
 

6.1. Błąd i jego obsługa.

• Instrukcja On Error
On Error Goto [nazwa etykiety]
Określa ona położenie bloku programu, który ma zostać wykonany gdy wystąpi błąd. Aby zapobiec wykonaniu się bloku obsługi błędu, gdy błąd się nie pojawi, przed tą etykietą powinna znajdować się instrukcja:
REM End Sub lub End Function <- • błąd zgłosił Szymon Nowak • 
Exit Sub lub Exit Function
On Error GoTo 0
Przechwytywanie błędów zostaje wyłączone i program po pojawieniu się błędu będzie reagował w sposób standardowy.
On Error Resume Next
Działanie programu będzie kontynuowane od instrukcji występującej bezpośredno za instrukcją powodującą błąd


' przykładowe wywołanie:
Private Sub DivisionByZero_1()
Dim X As Long

X = 0
X = 1 / X    ' pojawi się standardowe okno informujące o błędzie

On Error GoTo Err_ErrHandler
X = 1 / X    ' nastąpi skok do etykiety Err_ErrHandler
On Error GoTo 0
X = 1 / X    ' pojawi się standardowe okno informujące o błędzie

ExitHere:        ' punkt wyjścia z procedury
Exit Sub
Err_ErrHandler:
MsgBox " Opis błędu : " & Err.Description
Resume ExitHere: ' skocz do punktu wyjścia
End Sub

• Instrukcja Resume
Resume Next
Działanie programu będzie kontynuowane od instrukcji występującej bezpośredno za instrukcją powodującą błąd.
Resume [Etykieta]
Kontynuuje działanie programu od instrukcji określonej nazwą etykiety.
Resume
Działanie programu będzie ponownie kontynuowane od instrukcji powodującej błąd.

' przykładowe wywołanie:
Private Sub DivisionByZero_2()
On Error GoTo Err_ErrHandler
Dim X As Long

X = 1 / InputBox("Wprowadź liczbę różną od ZERA:", , 0)
' poniższe instrukcje zostana wykonane po wprowadzeniu prawidłowej wartości, jeżeli wystąpi błąd to tylko dla Resume Next w bloku obsługi błędu Err_ErrHandler:
X = X * X
MsgBox "Wynik = " & X

ExitHere:
Exit Sub
Err_ErrHandler:
MsgBox "Opis błędu: " & Err.Description
' 1. ponów wykonanie błędnej instrukcji
Resume
' 2. wykonaj następną instrukcję
' Resume Next
' 3. skocz do etykiety ExitHere (wyjście z procedury)
' Resume ExitHere:

End Sub

• Obiekt Err
Właściwości obiektu Err:
• Err.Number - numer aktualnego błędu,
• Err.Description - opis błędu,
• Err.Source - obiekt lub aplikacja, która wywołała błąd,
• Err.HelpFile - ścieżka dostępu do pliku pomocy,
• Err.HelpContext - identyfikator kontekstu dla tematu w pliku pomocy,
Err.LastDllError - systemowy kod błędu dla ostatniego wywołania biblioteki DLL
Metody obiektu Err:
• Err.Clear
    zeruje obiekt Err. - metoda ta jest automatycznie wywoływana:
    - zastosowaniu dowolnej instrukcji Resume,
    - zakończeniu procedury (Exit Sub, Exit Function, Exit Property),
    - zastosowaniu instrukcji On Error,
• Err.Raise
    powoduje wygenerowanie błędu, posiada następujące argumenty:
• Err.Number - numer wywoływanego błędu, jeżeli ma być wywołany własny błąd należy nadać mu własny numer = vbObjectError + Twoja_liczba
• Err.Description - opis błędu,
• Err.Source - obiekt lub aplikacja, która wywołała błąd,
• Err.HelpFile - ścieżka dostępu do pliku pomocy,
• Err.HelpContext - identyfikator kontekstu dla tematu w pliku pomocy,
przykładowo:
Err.Raise 11, "Funkcja A(...)", "Pamiętaj cholero, nie dziel przez zero !"
Err.Raise vbObjectError + 100, "Funkcja A(...)", "Znowu źle !"


' przykładowe zastosowania obsługi błędów:
Private Sub btnTest_Click()
On Error GoTo Err_ErrHandler
Dim snRet As Single
Const MY_FROM As Long = -10
Const MY_TO As Long = 10

OnceAgain:
' numeracja linii
1 snRet = zbTestErrRaise(-10, 10)
2 snRet = 1 / (snRet ^ 2)
3 MsgBox "Wynik: " & snRet

ExitHere:
Exit Sub
Err_ErrHandler:
Select Case Err.Number
Case ERR_CANCEL
' anulowano lub "", więc wyjdź
MsgBox Err.Description
Resume ExitHere
Case ERR_SMALL_NUMBER, ERR_LARGE_NUMBER
' za mało lub za dużo
MsgBox Err.Err.Description
Resume OnceAgain
Case ERR_MISMATCH
MsgBox "Wprowadź poprawną, numeryczną wartość !"
Resume OnceAgain
Case ERR_ZERO
' ustaw na zero i kontynuuj od następnej linii
snRet = 0
Resume Next
Case ERR_INVALID_ARGUMENT
' wprowadzno ujemną wartość z "naszego"
' prawidłowego zakresu -10 do 0,

' ustaw snRet na -1 i skocz do linii 3
snRet = -1
Resume 3
Case Else
MsgBox "Błąd nr : " & Err.Number & vbNewLine & _
"Źródło : " & Err.Source & vbNewLine & _
"Nieprzewidziany błąd, " & vbNewLine & _
"Opis błędu: " & vbNewLine & _
Err.Description
Resume ExitHere
End Select
End Sub

 ΔΔΔ 

 

6.2 Obsługa błędów na pl.comp.bazy-danych.msaccess.

grupa: pl.comp.bazy-danych.msaccess
wątek: Błędy we własnej klasie
autor pytania: Krzysztof Naworyta



<cyt>
Frapuje mnie taki problem:
Pisząc własną klasę, możemy obsługiwać błędy na sposób
1. "tradycyjny":
(...)
On Error Goto Err_Exit
(...)
Exit Sub/Function
Err_Exit:
Msgbox Err.Description, ...
2. przekazując błąd dalej, do procedury, która klasę wywołuje:
jeśli tu wystąpi znany błąd, albo po prostu jakaś znana lecz niechciana sytuacja, to:
Err.Raise ....
    Pytanie (może banalne) polega na tym, jak w tym drugim rozwiązaniu przekazać błędy nieprzewidziane ?
    Czy jedynym rozwiązaniem jest niestosowanie w ogóle przechwytu błędów w procedurze klasy ?
    A co w takim razie z błędami, które jednak wstępnie chcielibyśmy odsiać/zignorować już w samej klasie?
</cyt>

<cyt>
    w procedurze używam zarówno:
- On Error Resume Next
- On Error GoTo ...
- On Error Goto 0

i chciałbym aby procedura klasy zwracała ten błąd (lub nie) - w zależności w której partii procedury to się odbywa.
Ale problem wydaje się obecnie trywialny, a Wy mnie sprostujcie jeśli się mylę:

Jeśli używam:

On Error Resume Next
    to musze go jak najszybciej wyłączyć, najlepiej poprzez
On Error Goto 0
    Jeśli potem używam
On Error Goto Etykieta
    to w głównym ciele procedury nie używam
Err.Raise

bo to i tak zostanie przerzucone do obsługi błędów (no chyba, że chodzi o "moje" błędy) lecz dopiero w obsłudze błędu generuję ten błąd (jeśli taka moja wola)

On Error Goto Err_Exit
(...)
Err_Exit
Select Case Err.Number
Case ...
Err.Raise ...

W ten sposób zawsze mogę:
- zignorować przewidziany błąd (On Error Resume Next)
- wywołać własny przenosząc go do sekcji obsługi błędu
  (i albo przekazać do góry, albo wyświetlić jakiś MsgBox)
- to samo zdecydować z każdym innym przechwyconym błędem.

W tym drugim przypadku (jest /On Error GoTo Err_Exit/), jeśli użyję w głównej części procedury metody:
    Err.Raise
a następnie w obsłudze błędów będę chciał zdecydować o dalszym jej przekazaniu, to praktycznie muszę tę instrukcję powtórzyć (?)

    Cały czas próbuje rozszyfrować sens uzywania Err.Raise w klasach. Jakoś specjalnie akcentuje się, że w nich właśnie tą metodą należy się posługiwać. Całe życie bazowałem na "Select Case Err.Number" i MsgBox'ach. (zalecane jak najszybsze obsłużenie błędu)

    W przypadku klas rzecz wydaje się polega na tym, że twór taki ma wiele metod, niejednokrotnie uzależniających powodzenie od właściwej kolejności ich wywołania. Jeśli więc nie zajdzie ustawienie podstawowej właściwości, to nie mam prawa ustawiać właściwości następnych, czy egzekwować innych metod (przykładowo: nie mogę wywołać metody OpenRecordset, jeśli wcześniej nie ustawiłem obiektu Database)

    Najlepiej więc w takich sytuacjach przekazać błąd do procedury inicjującej klasę i niech ona się wywali, zamiast neutralizować błąd jakimś Message'em ...
</cyt>



grupa: pl.comp.bazy-danych.msaccess
wątek: Błędy we własnej klasie
autor odpowiedzi: Krzysztof Czuryło



<cyt>
To może popełnię mały wykładzik teoretyczny...

    Załóżmy, że piszę funkcję, która... np. służy do konwersji bitmapy na plik EMF (najlepiej z przezroczystym tłem ;-)). Funkcja wymaga jednego lub dwóch parametrów (ścieżka do pliku BMP i ścieżka do wynikowego pliku EMF). Żeby przykład był bardziej skomplikowany, powiedzmy że na razie funkcja wspiera tylko bitmapy 256-kolorowe (8-bitowe) i TrueColor (24-/32-bitowe).

No dobra, i co teraz?

Otóż taka funkcja może się "wyłożyć" z kilku powodów:
  1. plik BMP o podanej nazwie nie istnieje.
  2. plik istnieje, ale nie można go odczytać (np. brak praw odczytu)
  3. nie można utworzyć pliku EMF (brak praw zapisu)
  4. nie można utworzyć pliku EMF (brak miejsca na dysku)
  5. itd., itp.
    To są wszystko błędy, które wykryje sam Access i zgłosi jakiś standardowy błąd (MsgBox z odpowiednim komunikatem - w języku zależnym od wersji Accessa).

Do tego mogą jednak dojść sytuacje inne:
  1. Plik nie jest poprawną bitmapą (pomimo rozszerzenia BMP).
  2. Plik jest bitmapą, ale np. 16-kolorową, której nie wspieramy.
  3. itp., itd.
To są błędy, których Access nie wykryje, bo się na tym "nie zna".
Są to zatem błędy niestandardowe, które musimy wykryć (i obsłużyć) sami.
Co zrobić z błędami pierwszej kategorii?

1) Można spóbować wykryć i zarazem obsłużyć samemu. Np.
   If (Dir(PlikBMP) = "") Then
      MsgBox "Podany plik nie istnieje!"
   Else
      ... ' sprawdzanie innych "pułapek" (kolejne If-y) i wreszcie konwersja
   End If

2) Pozostawić wykrywanie błędu Accessowi, ale obsługiwać samemu:
   On Error Goto my_err
      ...
   my_err:
      MsgBox "..." ' lub coś innego

3) Ignorować lub obsługiwać "po cichu" (po prostu wyjść z funkcji nie robiąc nic).

   Sposób ostatni oczywiście nie jest dobrym rozwiązaniem.
To jest klasyczne zmiatanie śmieci pod dywan.

Z kolei sposób pierwszy (samodzielne wykrywanie) jest kiepskim pomysłem, gdyż:
  1. wszystkich sytuacji błędnych nie przewidzimy/nie wykryjemy
  2. kod się rozrasta (dużo If-ów), a przecież w 99% przypadków błąd nie wystąpi, więc 99 razy na 100 wykonujemy te If-y niepotrzebnie.
  3. w momencie wykonania If-a plik może istnieć, ale już linijkę dalej, przy próbie otwarcia, może się okazać, że go nie ma. Windows to system wielozadaniowy, sieciowy. Inny proces/użytkownik może zmienić status pliku, prawa dostępu, skasować plik, etc. _w_każdej_chwili_, np. właśnie pomiędzy wykonaniem tych naszych dwóch linijek kodu.
    Słowem, lepiej samemu nie bawić się w wykrywanie błędów, które Access jest w stanie wykryć sam.

    No dobra, Access wykrywa błędy, a co z własną obsługą (sposób drugi)?
Tu sprawa jest bardziej skomplikowana. Zwróćmy uwagę, że jeśli funkcja jest ZAWSZE wywoływana np. naciśnięciem przycisku i używamy jej tylko w jednej aplikacji, to możemy swobodnie przechwycić błąd (On Error Goto) i wyświetlić jakiś "nasz" komunikat.

    Co się jednak stanie, gdy naszą funkcję (moduł) upublicznimy?
Co jeśli będzie z niej korzystał np. jakiś Hiszpan lub po prostu autor innej aplikacji?

    Na co mu nasz (polski) komunikat? To już lepiej pozostać przy standardowym komunikacie Accessa.
    Po drugie, funkcja może być przecież wołana w pętli np. dla 200 róznych plików BMP.
To, że sami tak nie robimy nie oznacza, że ktoś inny nie zechce. Jeśli przy każdym błędzie będziemy męczyć użytkownika MsgBox'em, to trochę się wkurzy. (Np. gdy na 200 podanych plików 70 nie istnieje = 70 kliknięć).

Jaki z tego wniosek?

    Funkcja/procedura/klasa "uniwersalna" nie powinna w żaden sposób ingerować w obsługę standardowych błędów. Ta obsługa powinna być zaimplementowana w kodzie, który WOŁA daną funkcję, korzysta z klasy, itp. Jeśli zechcę przy każdym błędzie pokazywać komunikat, to tak zrobię. Może nawet zmienię jego treść (na taką, jaka MI odpowiada). Jeśli zaś dla odmiany będę funkcję wołał w pętli, to będę przechwytywał błędy "po cichu", zaloguję nazwy nieistniejących plików i wyświetlę je na końcu zbiorowo.
    Nie chcę, by autor funkcji/klasy decydował za mnie jak obsługiwać błędy, jakie komunikaty wyświetlać, itp. Jeśli to robi, to taka funkcja/klasa jest mało uniwersalna i raczej nieprzenośna.

Podsumujmy:
    W funkcjach/modułach/klasach ogólnego przeznaczenia (pisanych z myślą o wykorzystaniu w różnych aplikacjach) NIE implementujemy ŻADNEJ własnej obsługi _błędów_standardowych_.
• Nie próbujemy też wyręczać Accessa w ich wykrywaniu.
• Nie staramy się również ich przechwytywać i obsługiwać po swojemu - niech o tym decyduje konkretne zastosowanie naszego kodu.
Słowem - żadnych "On Error Goto"!

    Są zapewne wyjątki, ale IMHO taka powinna być reguła. Wyjątkowo możemy np. przechwycić błąd po to, by spróbować wykonać jakąś akcję raz jeszcze (do trzech razy sztuka ;-)).
Wtedy miałoby to jakiś sens, ale ostatecznie i tak w którymś momencie błąd musi zostać przekazany wyżej.

Teraz druga kategoria błędów - błędy niestandardowe.

    Tutaj sprawa też jest jasna - wykryć musimy je sami. Dla podanego przykładu, trzeba np. przeanalizowac nagłówek pliku i podjąć decyzję, że plik jest poprawną bitmapą i na dodatek spełniającą nasze kryteria.

    No i jeśli nie jest, to... jak nietrudno się domyślić, nie powinniśmy bynajmniej wyświetlać komunikatów w stylu "Podany plik nie jest poprawną bitmapą!", ani nie powinniśmy tego faktu ignorować. (Znowu może się zdarzyć, że ktoś postanowi naszą funkcję wołać w pętli dla 200 plików.) Trzeba zamiast tego poinformować o błędzie "warstwę wyższą", t.j. wygenerować błąd i pozostawić jego obsługę procedurze, która naszą funkcję wywołała.
Do tego służy właśnie Err.Raise. Amen!!!

    Skoro nie obsługujemy błędów w funkcjach/klasach, tylko przekazujemy je "wyżej", na dodatek dorzucając czasem swoje niestandardowe błędy (Err.Raise), to gdzie je w końcu obsługiwać?
    Otóż w praktyce na ostatnim możliwym etapie, czyli najczęściej w procedurach zdarzeń.
Jeśli tam nie obsłużymy błędu, to on już nie zawędruje nigdzie dalej, tylko znowu trafi do Accessa i wywoła odpowiednią reakcję. Tak więc w procedurach zdarzeń jak najbardziej stosujmy "On Error Goto/Resume", za to Err.Raise nie na wiele nam się tu zda - właściwie ten sam efekt da zwykły MsgBox.

    Przy okazji - bo może nie każdy wie - "On Error ..." możemy stosować wielokrotnie w obrębie JEDNEJ funkcji/procedury.

Public Sub Test()
Dim a, b, c As Byte

   a = 5
   b = 0
   c = 100

' od tej pory olewamy błędy
On Error Resume Next
   c = a / b
' teraz wyświetlamy komunikat
On Error GoTo err1
   c = a / b
' teraz znowu olewamy
On Error Resume Next
   c = a / b
' a teraz obsługujemy, ale inaczej
On Error GoTo err2
   c = a / b

Exit Sub

err1:
   MsgBox "Jakiś błąd!"
   Resume Next
err2:
   MsgBox "Jeśli b = 0 -> b = 1"
   b = 1
   Resume
End Sub

    Jeśli zatem spodziewamy się, że w pewnym fragmencie kodu mogą wystąpić błędy, które chcemy zignorować/obsłużyć, to możemy to zrobić dla tego wybranego kawałka kodu.
   --
   KrzyCz
</cyt>

 ΔΔΔ 

 

6.3 Błąd w MS Access - jak pobrać numery błędów i ich opisy ?

grupa: pl.comp.bazy-danych.msaccess
wątek: obsługa błędów pomocy...... Opcje
w oparciu o artykuł: Krzysztofa Naworyty



<cyt>
Private Sub knListAccessErrors()
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
Dim i As Long
Dim sErrDescr As String

Set dbs = CurrentDb
dbs.Execute "CREATE TABLE tErrors " & _
"(ErrNo LONG, ErrDescr MEMO);"
Set rst = dbs.OpenRecordset("tErrors", , dbAppendOnly)

For i = 1 To 65535
sErrDescr = AccessError(i)
If Not (Len(sErrDescr) = 0 Or _
sErrDescr Like "Application-defined*") Then
With rst
.AddNew
!ErrNo = i
!ErrDescr = sErrDescr
.Update
End With
End If
Next
rst.Close

Set rst = Nothing
Set dbs = Nothing

'    Jak zauważysz, w powyższej tabeli (tErrors) znajdziesz komunikaty typu:
' "Już istnieje otwarty obiekt bazy danych o nazwie '|'.@ Użyj różnych nazw dla każdego obiektu ..."
' Access w miejsce "fajki" wstawia nazwę owego obiektu (w innym przypadku nazwę pola, gdzie np. naruszono regułę poprawności).
' Ale Ty tej nazwy z komunikatu nie odzyskasz.....

End Sub
</cyt>

 ΔΔΔ 

 

6.4 Jak pobrać opis błędu wywołanego podczas uruchamiania funkcji API,
na przykładzie błędnych argumentów wywołania funkcji ShellExecute (...) ?

Private Declare Function FormatMessage Lib "kernel32" _
Alias "FormatMessageA" _
(ByVal dwFlags As Long, _
lpSource As Any, _
ByVal dwMessageId As Long, _
ByVal dwLanguageId As Long, _
ByVal lpBuffer As String, _
ByVal nSize As Long, _
Arguments As Long) As Long
Private Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000
Private Const FORMAT_MESSAGE_IGNORE_INSERTS = &H200
' przykładowe funkcje testowe:
Private Declare Function ClosePrinter Lib "winspool.drv" ( _
ByVal hPrinter As Long) As Long
Private Declare Function CopyFile Lib "kernel32.dll" _
Alias "CopyFileA" _
(ByVal lpExistingFileName As String, _
ByVal lpNewFileName As String, _
ByVal bFailIfExists As Long) As Long
Private Declare Function ShellExecute Lib "shell32.dll" _
Alias "ShellExecuteA" _
(ByVal hWnd As Long, _
ByVal lpOperation As String, _
ByVal lpFile As String, _
ByVal lpParameters As String, _
ByVal lpDirectory As String, _
ByVal nShowCmd As Long) As Long
Private Const SW_SHOWNORMAL = 1



' 1. Na podstawie MSDN - Article ID: Q170918
' Wiedząc, że funkcja ShellExecute (.) zwraca przy błędzie wartość <= 32
' można zrobić Select'a i do każdego opisanego błędu przypisać komunikat.

Private Function MSDN_Q170918(lErrNo As Long) As String
Dim sRet As String
Const SE_ERR_FNF = 2&
Const SE_ERR_PNF = 3&
Const SE_ERR_ACCESSDENIED = 5&
Const SE_ERR_OOM = 8&
Const SE_ERR_DLLNOTFOUND = 32&
Const SE_ERR_SHARE = 26&
Const SE_ERR_ASSOCINCOMPLETE = 27&
Const SE_ERR_DDETIMEOUT = 28&
Const SE_ERR_DDEFAIL = 29&
Const SE_ERR_DDEBUSY = 30&
Const SE_ERR_NOASSOC = 31&
Const ERROR_BAD_FORMAT = 11&

' There was an error
Select Case lErrNo
Case SE_ERR_FNF
sRet = "File not found"
Case SE_ERR_PNF
sRet = "Path not found"
Case SE_ERR_ACCESSDENIED
sRet = "Access denied"
Case SE_ERR_OOM
sRet = "Out of memory"
Case SE_ERR_DLLNOTFOUND
sRet = "DLL not found"
Case SE_ERR_SHARE
sRet = "A sharing violation occurred"
Case SE_ERR_ASSOCINCOMPLETE
sRet = "Incomplete or invalid file association"
Case SE_ERR_DDETIMEOUT
sRet = "DDE Time out"
Case SE_ERR_DDEFAIL
sRet = "DDE transaction failed"
Case SE_ERR_DDEBUSY
sRet = "DDE busy"
Case SE_ERR_NOASSOC
sRet = "No association for file extension"
Case ERROR_BAD_FORMAT
sRet = "Invalid EXE file or error in EXE image"
Case Else
sRet = "Unknown error"
End Select

MSDN_Q170918 = sRet

End Function
' Stosując powyższą metodę, to dla każdej wywoływanej funkcji trzeba
' by pisać własną obsługę błędów.



' 2. Korzystając z właściwości Err.LastDllError
'     spróbujmy otrzymać opis ostatniego błędu.

Private Function zbLastDllErrorDescr(lNoLastError As Long, _
Optional sFunctionName As String = "") As String
Dim sErrMsg As String
Dim lRet As Long
Const MY_BUFFSIZE As Long = 256

sErrMsg = String(MY_BUFFSIZE, vbNullChar)
lRet = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM Or _
FORMAT_MESSAGE_IGNORE_INSERTS, 0&, _
lNoLastError, 0&, sErrMsg, MY_BUFFSIZE, ByVal 0&)
If Len(sFunctionName) = 0 Then
zbLastDllErrorDescr = Left$(sErrMsg, lRet)
Else
zbLastDllErrorDescr = "Błąd nr " & lNoLastError & " funkcji " & _
sFunctionName & vbNewLine & _
"Opis błędu: " & vbNewLine & Left$(sErrMsg, lRet)
End If

End Function


' przykładowe wywołanie:
Private Sub btnTest_Click()
Dim sPath As String
Dim sDir As String
Dim ff As Integer
Dim lRet As Long
Dim sRet As String
Const MY_FILE_PATH As String = "/MojPlik.txt"
Const MY_FILE_FAIL_EXT As String = "/MojPlik.śćą"

sDir = Environ$("TEMP")

' utwórz pusty plik *.txt
ff = FreeFile
Open sDir & MY_FILE_PATH For Binary Access Write As #ff
Close #ff

' utwórz pusty plik z nieskojarzonym rozszerzeniem *.śćą
ff = FreeFile
Open sDir & MY_FILE_FAIL_EXT For Binary Access Write As #ff
Close #ff

' Test 1. - otwieramy nieistniejący plik
' - (sDir & MY_FILE_PATH & "XXX")

Debug.Print "Test 1. "; String(55, "=")
lRet = ShellExecute(hWndAccessApp, "Open", _
sDir & "XXX" & MY_FILE_PATH, _
vbNullString, vbNullString, SW_SHOWNORMAL)
If lRet <= 32 Then
Debug.Print "M$: "; MSDN_Q170918(lRet)
Debug.Print "ZB: "; zbLastDllErrorDescr(Err.LastDllError)
End If

' Test 2. - otwieramy istniejący plik,
' ale folder (dysk) domyślny "Z:\" nie istnieje

Debug.Print "Test 2. "; String(55, "=")
lRet = ShellExecute(hWndAccessApp, "Open", _
sDir & MY_FILE_PATH, _
vbNullString, "Z:\", SW_SHOWNORMAL)
If lRet <= 32 Then
Debug.Print "M$: "; MSDN_Q170918(lRet)
Debug.Print "ZB: "; zbLastDllErrorDescr(Err.LastDllError)
End If

' Test 3. - otwieramy istniejący plik, który posiada
' nieskojarzone rozszerzenie *.śćą

Debug.Print "Test 3. "; String(55, "=")
lRet = ShellExecute(hWndAccessApp, "Open", _
sDir & MY_FILE_FAIL_EXT, _
vbNullString, vbNullString, SW_SHOWNORMAL)
If lRet <= 32 Then
Debug.Print "M$: "; MSDN_Q170918(lRet)
Debug.Print "zb: "; zbLastDllErrorDescr(Err.LastDllError)
End If

' Test 4. Zamknij printer, który nie jest otwarty
Debug.Print "Test 4. "; String(55, "=")
lRet = ClosePrinter(-123456789)
If lRet = 0 Then
Debug.Print zbLastDllErrorDescr( _
Err.LastDllError, " - Zamykania printera")
End If

' Test 5. kopiuj nie istniejący plik
Debug.Print "Test 5. "; String(35, "=")
lRet = CopyFile("z:\aaa.aaa", "z:\aa.aaa", 0)
If lRet = 0 Then
Debug.Print zbLastDllErrorDescr( _
Err.LastDllError, " - Kopiowanie pliku")
End If
Debug.Print String(63, "=")
DoCmd.RunCommand acCmdDebugWindow

End Sub

 ΔΔΔ