General / Generic Error Handling lazarus / 62-General ---------------------------------------------------------------------- joho · 2017-08-28 17:45 I'm new to FP, but have previously been using TP, BP, Delphi, VirtualPascal, etc. Are Exceptions to be considered best practice in the FP environment for error handling? Or is there some error handling / error code enumeration function that I have missed? I'm trying to port a rather big applications where errors were previously handled by returning an error code, and then the error code would be enumerated to an error message and displayed / logged / whatever. Depending on how serious the error was, the application may continue to run or issue a "Fatal Abort". Should this be done with Exceptions in the FP world, or are there other ways? ---------------------------------------------------------------------- marcov · 2017-08-28 21:20 FPC has two faces, one more TP like and one more Delphi side, so it is a matter of choosing. However, even with Delphi, exceptions are mainly meant to pass exceptions in business code up to higher level to keep it simple and cheap. System and more performing code generally avoid exceptions, and handle errors locally as much as possible. (see e.g. trystrtoint) Or do you mean system/runtime errors ? What do you exactly miss from TP/Delphi? ---------------------------------------------------------------------- joho · 2017-08-28 21:29 I think a lot of my initial queries come from not being used to playing with FP enough (yet). A lot of the code I'm porting, is based around my own encapsulating units, classes, and functions. They in turn call the underlying stuff, much like an RTL separates the programmer from the OS I guess. When I did the Virtual Pascal (for OS/2) port of this, I tried to emulate as much of what I was already doing into a set of libraries for VP so I didn't have to change too much of the application code. That worked out quite nicely. This time, I've decided (almost) to go the other way. FP will most likely be the last tool used for the Pascal version of the code, so I try to look for "native" FP functions that does what I need and then take out all IFDEF that I can and let FP's RTL handle the differences. The problem is that I seem to need to learn the entire FCL, RTL, etc by heart ... or die from lack of patience Psuedo code of what I'm used to: f := OpenFile ('foo.txt', rO | dW); if (f < 0) then   begin     if (errno = ENOENT) then       f := CreateFile ('foo.txt', rW | DW)     else        DieFatallyAndMakeNoises ('foo.txt', errno);   end else   .. .. ... ... So in this particular case, I guess I was looking for a standardized set of error codes, 1 - nnnnnn, that can be used universally between all units, on all platforms, to mean the same thing; to be reported to the user, or the log file, or whatever. (Sorry if this sounds like incoherent ramblings, I've got 32 browser tabs open and three PDF files trying to make sense of the entire structure ) ---------------------------------------------------------------------- marcov · 2017-08-28 21:40 FPC does not emulate POSIX on win32. The best principle is to use system (in delphi mode) and sysutils function as much as possible, that avoids 255-260 char length problems, and even has some unicode capabilities. The errorcodes are mostly system specific though, but afaik OS/2 and win32 are fairly similar there for the base errors, and on Unix it will be mostly the errno returnvalues, so that is fairly straightforward. ---------------------------------------------------------------------- joho · 2017-08-28 21:44 Right ... but I'd still need to map all this to a "generic" set of values that are IFDEFd around system specifics, right? Something like (psuedo): ec_FileNotFound = 1; ec_PathNotFound = 2; function mapOStoGeneric (ecOS : longword): longword; begin   ifdef os2       case enoent: mapOStoGeneric := ec_FileNotFound;       case enopath: mapOStoGeneric := ec_PathNotFound;   elifdef linux       case fooFiienoent: mapOStoGeneric := ec_FileNotFound;       case FooFiienopath: mapOStoGeneric := ec_PathNotFound;   elifdef win32   elifdef dos   endif end; and so on ... to get some sort of abstraction. ---------------------------------------------------------------------- joho · 2017-09-01 06:54 So for all FileOpen and FileCreate, for example, the only way of knowing that something went wrong is to look at the handle and see if it's = feInvalidHandle? How do I know if the error was due to an invalid path, access denied, or some other error ... ? Quote from: marcov on August 28, 2017, 11:40:13 pmFPC does not emulate POSIX on win32. The best principle is to use system (in delphi mode) and sysutils function as much as possible, that avoids 255-260 char length problems, and even has some unicode capabilities. The errorcodes are mostly system specific though, but afaik OS/2 and win32 are fairly similar there for the base errors, and on Unix it will be mostly the errno returnvalues, so that is fairly straightforward. ---------------------------------------------------------------------- Thaddy · 2017-09-01 08:56 Quote from: joho on September 01, 2017, 08:54:56 am So for all FileOpen and FileCreate, for example, the only way of knowing that something went wrong is to look at the handle and see if it's = feInvalidHandle? https://freepascal.org/docs- html/current/user/userap4.html#x190-197000D So absolutely not. Fully cross platform. Basic capability deficiency. Will be OK in your future...in most but not all cases... You may also want to have a look at IOResult and {$I+/-} in the manuals... Actually, contrary to most programmers, I find manuals a rather good idea? ---------------------------------------------------------------------- joho · 2017-09-01 14:38 OK, thanks. That makes sense. I just got a bit confused. I remember the IOResult handling, etc. from way before I put my own I/O routines everywhere, mostly translated from how they work in C/C++ (the handle I/O kind). I guess I'm back to doing it the "Turbo Pascal" way now, even for the handle based functions So a FileOpen with {$I-} will then generate an RTE from one of those mentioned, if I understand you correctly. Just like the "standard Turbo Pascal I/O" functions? ---------------------------------------------------------------------- Thaddy · 2017-09-01 15:32 Quote from: joho on September 01, 2017, 04:38:27 pmSo a FileOpen with {$I-} will then generate an RTE from one of those mentioned, if I understand you correctly. Just like the "standard Turbo Pascal I/O" functions? Exactly! Glad to be of help. ---------------------------------------------------------------------- joho · 2017-09-01 15:33 Thanks ---------------------------------------------------------------------- marcov · 2017-09-01 15:39 Quote from: joho on September 01, 2017, 08:54:56 am So for all FileOpen and FileCreate, for example, the only way of knowing that something went wrong is to look at the handle and see if it's = feInvalidHandle? Well, that is what Embarcadero specifies, and worse they even discourage these functions, see e.g.  http://docs.embarcadero.com/prod ucts/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/Sy sUtils_FileOpen.html So why would FPC be different ? (afaik these were originally Win16-32 related winapi wrappers for TPW->D1->D2 changeovers, and never meant to be published apis) QuoteHow do I know if the error was due to an invalid path, access denied, or some other error ... ? Use ioresult the old fashioned way. IOResult does have some error mapping. But use the Delphi equivalents AssignFIle etc. See https://www.freepascal.org/docs-html/rtl/system/ioresult.html Though not all will happen for all targets. ---------------------------------------------------------------------- joho · 2017-09-01 15:46 This is messy. To put it mildly. So ... what is the best, and most portable (FPC style) way of doing I/O *with* proper error control? VAR   FH :THandle; {$I+} FH := FileOpen ('C:\NotHere.Txt', fmOpenWrite or fmShareDenyWrite); writeln ('IOResult ', IOResult); writeln (FH); FileClose (FH); writeln ('IOResult ', IOResult); {$I-} I would have expected a RTE in at least two places here. 1) When I open the file, since it's not here, and 2) definitely when I try to close an invalid file handle. There are many many many thousand lines of code in this application, I wasn't planning on re-writing all the I/O code on top of everything else that needs fixing ...  I realize I can't get away with not knowing *some* quirks, but I don't really want to spend all my porting time learning about quirks. I just want to port the application to a Win32 (or Win64 ) console application using FPC, and then take it from there ... ---------------------------------------------------------------------- joho · 2017-09-01 15:51 I don't quite understand their comment about "Low Level" either ... what's Low Level about Handle := Open (File, Mode)?! You do the same thing when you use an object based class construct like MyStream := New MyStreamClass (Filename, Mode) ... it's just an other way of encapsulating the "low level" calls because you want more logic in your class than the OS I/O functions provide. ---------------------------------------------------------------------- Thaddy · 2017-09-01 16:20 Marco has lost me too... This was a design goal for FPC: TP compatibility. Which it is, apart from some 16/32 bit niggles. ---------------------------------------------------------------------- marcov · 2017-09-01 17:39 Quote from: joho on September 01, 2017, 05:51:15 pmI don't quite understand their comment about "Low Level" either ... what's Low Level about Handle := Open (File, Mode)?! Afaik it was a temporary routine to keep TPW code running, where people programmed closer to the winapi. I'm no D1/TPW expert though (I skipped Win3.x entirely, at least for programming purposes). IOW it never was what you thought it to be, a general, supportable delphi function. FPC never implemented, and the Delphi help also shows this fact. Keep in mind that deprecated tags on procedural level would not emerge till D2006 or so. You are not the first, you will not be the last (fileexists on Directories "works" in Delphi even though not documented, however emulating it for the few that didn't care to use undocumented feature will tragically slow down on *nix) QuoteYou do the same thing when you use an object based class construct like MyStream := New MyStreamClass (Filename, Mode) ... it's just an other way of encapsulating the "low level" calls because you want more logic in your class than the OS I/O functions provide. It's not procedural vs OOP, but documented procedural (assignfile+reset/rewite, documented codes in ioresult) vs semi documented procedural (fileopen, no errorcodes documented) ---------------------------------------------------------------------- marcov · 2017-09-01 17:41 Quote from: Thaddy on September 01, 2017, 06:20:57 pmMarco has lost me too... This was a design goal for FPC: TP compatibility. Which it is, apart from some 16/32 bit niggles. Just look at the Delphi help, and specify which part FPC doesn't support. ---------------------------------------------------------------------- ASerge · 2017-09-01 18:12 Quote from: joho on September 01, 2017, 05:51:15 pmI don't quite understand their comment about "Low Level" either I do not know the function OpenFile. Only SysUtils.FileOpen Low level: var H: THandle; H := FileOpen(...); The RTL does not check the result, nor does it handle the errors. You have to do it yourself. High level (classic old style) var F: File; AssignFile(F...); Reset(F); Inside it calls something like FileOpen, saves the result to the System.InOutRes variable. If it's incorrect, in the case of the {$I+} state, throws an exception. High level (new style) var S: TFileStream; S := TFileStream.Create(...); Inside it calls something like FileOpen, checks the result. If it's incorrect get an error message from the system and throws an exception. ---------------------------------------------------------------------- joho · 2017-09-02 05:04 Sorry if I mistyped the name ...  Well, I guess I'll just have to decide between porting my generic I/O stuff first and then roll with that instead, or using "undocumented stuff in FPC", or using "traditional and documented pascal I/O". Handle based I/O makes more sense to me, whether or not the "handle" is an integer or an object reference, and I've never been a fan of TP's traditional I/O handling. Thanks everyone for responding. I'm not trying to sound ungrateful, I'm just trying to make sense of best practice, etc. ---------------------------------------------------------------------- joho · 2017-09-02 05:21 GetLastOSError is interesting though. That very much looks like C-style "errno" and is properly updated when, for example, FileOpen () fails. Maybe I should continue studying the RTL sources instead ---------------------------------------------------------------------- marcov · 2017-09-02 08:30 Quote from: joho on September 02, 2017, 07:21:06 amGetLastOSError is interesting though. That very much looks like C-style "errno" and is properly updated when, for example, FileOpen () fails. Maybe I should continue studying the RTL sources instead Afaik getlastoserrror is either FPC's internal errno (on *nix) or just getlastwinerror function on windows. However the problem is that that assumes fileopen is open call, error results can be read always and multiple times AND the result will be OS dependent again. ---------------------------------------------------------------------- joho · 2017-09-03 04:49 I don't mind if "errno" is OS dependant. I have code to map the DOS, OS/2 and Windows error codes into something generic. Some codes don't exist on some OSs, and that's OK too. I haven't been able to locate where FileOpen calls the OS though ... (in the RTL sources I mean), but I will keep looking ---------------------------------------------------------------------- molly · 2017-09-03 05:19 @joho: Inside sysutils (depends on platform where this file is located in source-tree), you can find fileOpen. For windows for example it reads: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Function FileOpen (Const FileName : unicodestring; Mode : Integer) : THandle;begin  result := CreateFileW(PWideChar(FileName), dword(AccessMode[Mode and 3]),                       dword(ShareModes[(Mode and $F0) shr 4]), nil, OPEN_EXISTING,                     FILE_ATTRIBUTE_NORMAL, 0);  //if fail api return feInvalidHandle (INVALIDE_HANDLE=feInvalidHandle=-1)end;