Exceptions and DLL in Delphi - delphi

What is the right way to handle exceptions thrown from inside a DLL in Delphi?
Something like this
on E : ESomeException do ...
or
if (E is ESomeException) then ...
fails, because of the separate type registries of the DLL and the main application.

For pure DLL's exceptions are not allowed to cross the DLL boundary (like Deltics mentions) - no matter what language.
You get all sorts of trouble there, especially because you don't know which language, RTL, memory manager, etc, is on each side of the boundary.
So you are back to the classic error handling paradigm:
error codes (similar to HResult)
error messages (similar to GetLastError)
Instead of DLL's, you could use BPL packages (as Lars suggested): there you know that both sides will use the same RTL and memory manager.
Both packages and BPL usually give you a versioning nightmare anyway (too many degrees of freedom).
A more rigorous solution is to go for a monolithic executable; this solves both problems:
much easier versioning
guaranteed only one RTL and memory manager
--jeroen
PS: I've made this an additional answer because that allows for easier pasting of links.

The safest way is to not allow exceptions to "escape" from the DLL in the first place.
But if you have no control over the source of DLL and are therefore unable to ensure this, you can still test the exception class name:
if SameText(E.ClassName, 'ESomeException') then ...

If you use runtime packages (at least rtlxx.bpl) for both your application and your dll, then both have the same type and it will work. Of course this limits the use of your dll to Delphi/BCB only.
Another solution is not using exceptions at all like Deltics suggest. Return error codes.
Or use COM. Then you can have exceptions and not limit your dll to Delphi only.

Sometimes you do not have control over a DLL and cannot avoid having trouble with exceptions.
We, for instance, had a problem with a function in an external DLL that was blocked by AV software settings ("ransomware protection") leading to access violations in Delphi.
The following code is working for us:
var
O: TObject;
Msg: AnsiString; //type depending on DLL
begin
try
CallExternalDll(...);
except
//on E: Exception do might lead to access violations
//because the exception might not be of type Exception
O := ExceptObject;
//Depending on the DLL this or an other cast might
//or might not be necessary:
Msg := PAnsiChar(Exception(O).Message);
raise Exception.Create(Msg);
end;
end;

This workaround seems to do it for me:
function ExceptionMatch (Exc : Exception; ExcClass : TClass) : Boolean;
var
CurrClass : TClass;
begin
CurrClass := Exc.ClassType;
while (CurrClass <> nil) do
begin
if SameText (CurrClass.ClassName, ExcClass.ClassName) then
Exit (True);
CurrClass := CurrClass.ClassParent;
end;
Result := False;
end;
I'm prepared for you to destroy this :)
What is wrong with this approach? What is potentially dangerous?

Related

How to determine dependencies for all units? (Estimate refactoring cost)

Unfortunately I have inherited a piece of unlucky design based on a {$IFDEF} "hell".
What I have roughly looks like this (where MyDbSystemB is what I want to add)
unit MyTable;
interface
uses
// ...
{$IFDEF MyDbSystemA}
DbSystemA ,
{$ENDIF}
{$IFDEF MyDbSystemB}
DbSystemB ,
{$ENDIF}
type
TMyTable = class(
{$IFDEF MyDbSystemA}
TSystemATable
{$ENDIF}
{$IFDEF MyDbSystemB}
TSystemBTable
{$ENDIF}
)
// A public interface implementation
end;
implementation
// ...
end.
A number of units reference TMyTable, but rely on specific functions provided with TSystemATable:
unit oldUnit;
interface
uses MyDbTable;
type
TXy = class(TXyz)
public
procedure Foo();
end;
implementation
procedure TXy.Foo();
var
table : TMyTable;
begin
table := TMyTable.Create();
table.SomeSystemASpecificFunction;
end;
I'd like to find all of these references within a single reference/syntax check. But as I read here that's apparently not really possible
Find all compilation errors in a Delphi project.
What would be the best strategy to go for finding these files to estimate the efforts of porting?
A plain file grep over all *.pas files (there may be a lot of either dead code, or just poorly decoupled stuff)?
I'm also able to provide a surrogate implementation of
TMyTable = class( { ... like above } )
{$IFDEF MyDbSystemB}
public
procedure SomeSystemASpecificFunction; deprecated;
{$ENDIF}
end;
with the implementation of TMyTable, but I'd need to estimate the refactoring cost to do that properly anyways.
Regarding that I also could add a deprecated attribute along with the SomeSystemASpecificFunction surrogate, that will at least give me warnings.
If you know the names of the members of TMyTableA that code takes a dependency on then use Find in Files... (or your favorite alternative grep-like tool) to identify references to those members in the files in your project.
This is likely to be more reliable than any compilation based check anyway.
Any tool which claims to find "all compilation errors in a project" is more often than not lying to you since there is no reliable way to discriminate between errors that have not arisen as a result of some other error earlier in the compilation.
e.g. in a C# solution it is quite common for a simple change to result in dozens if not hundreds of compilation errors which are fixed by resolving only the first error.
i.e. the compiler reports (e.g.) 224 errors when in fact there is only 1 error with 223 errors as a side-effect.
For the same reason, you cannot be sure that the list of errors contains all of the genuine errors you might eventually uncover. In some cases the compiler might yet be defeated to the point of not even attempting to compile some code that contains the errors you are looking for, as a consequence of those side-effect errors.
Certainly, in the list of dozens or hundreds of errors you might then be able to grep to find ones that appear to be candidates for the "genuine" errors you are looking for (i.e. that reference the specific members you know to be involved in your change). But all that does is change the data set you are searching for to locate those references. i.e. the compilation error list rather than your project sources.

Advantage of creating an Access-Violation for testing purposes with asm code?

We recently started using madExcept instead of ExcMagic as our Exception Handler.
We are also working on a 64 bit build of our app and therefore would like to eliminate, if possible, uses of asm code.
ExcMagic had this procedure to create an Access Violation for testing purposes.
procedure CreateAccessViolation
begin
asm
mov eax,11111111h
mov ebx,22222222h
mov ecx,33333333h
mov edx,44444444h
inc dword ptr [eax]
end;
end;
Is there any advantage to using that code instead of just writing
raise EAccessViolation.Create('Just testing...');
There must be some reason the ExcMagic developers used that.
Thanks!
Exceptions, at the OS level, are not objects. They're numeric values that might carry additional context data. When you attempt to read or write an invalid address, you get exception code Exception_Access_Violation with context giving the attempted operation and address, but when you use the raise reserved word, you get exception code cDelphiException with connect giving a Delphi object reference. (Did you know you can throw any object, not just descendants of Exception?)
In Delphi parlance, an OS-level exception is referred to as a run-time error.
There's code in the SysUtils unit that will transform an access-violation run-time error, as created by your assembly code, into an EAccessViolation Delphi exception. That makes it easy to catch with Delphi's try-except syntax.
If there's non-Delphi code on the call stack (or maybe even Delphi code that isn't using SysUtils), it probably won't recognize the cDelphiException exception code, and even if it does, it might not be able to differentiate one Delphi object type from another. Exception_Access_Violation, on the other hand, is known throughout all Windows code.
If you want to test behavior from an access violation, it's probably best for there to be an actual violation of access. That's what the assembly code attempts to do. (Whether It succeeds at that is a separate issue.) If you want to test the catching of Delphi objects, then throw whatever objects you want, including instances of EAccessViolation.
The access violation lists the address that was accessed (in this case $11111111).
Because its such an unusual address that would stick out.
If you look at a stack trace it usually lists the values of the registers at the time of the crash, because of their unusual values this is again a red flag.
My guess is that the codebase uses this to differentiate between its own exceptions and other exceptions by checking the magic numbers.
However this seems to me a very silly way of faking a custom exception.
A more informative approach would be to make your own class of exception and raise that.
type
EMadException = class(Exception)
//custom fields, constructors, properties, etc can be added
end;
begin
raise EMadException.Create('info');
end;
In the error dialog it will clearly state the name of the exception and any info you've provided.
This also makes it trivial to differentiate between your exceptions and everybody else's:
try
DoSomething
except
on E: EMadException do ShowMessage('it''s mine');
else ShowMessage('not mine');
end;

Delphi -make a function available to other forms beside mainform

I use a function to log application errors to a file.I have it on my main form.How do I tell my other forms, in case of an error,to use this function? Just by chance I added this to my query on some other form:
......
try
ClientDataSet1.Execute;
ClientDataSet1.Close;
except
on E:Exception do begin
ShowMessage('Error : ' + E.Message);
LogError(E);
....
My LogError(E); is not recognized (gives error). Tried adding the function :procedure LogError(E:Exception); to public uses but wont work.
Structured Exception Handling
I'd like to point out that your approach to exception handling is very tedious and wasteful coding. Not to mention quite dangerous. You don't want to be littering your code with individual handlers to log your exceptions when there's a much more practical approach.
Fortunately Delphi provides the means to make this much easier. First a little background about structured exception handling... Most developers make the mistake of trying to deal with errors by writing code like the following:
try
//Do Something
except
//Show Error
//Log Error
end;
The problem with the above code is that it swallows the error. Even though the user sees an error message, the error itself is hidden from the rest of the program. This can result in other parts of the program doing things they should not do because of the error.
Imagine a program that calls the following routines: LoadAccount; ApplyDiscount; ProcessPayment;. But the the first routine raises an error. Which a programmer (mistakenly thinking they're being diligent) decided to "handle" as above. The problem is that the next two routines will still be called as if nothing is wrong! This could mean that a discount is applied to and payment processed for the wrong account!
The point is that structured exception handling saves us from these headaches (provided we don't break the way it works). If the LoadAccount routine didn't try "handle" the exception, then while the application is in an "exception state" the code would simply keep jumping out of routines until it finds a finally / except handler.
This suits an event driven program very nicely.
Suppose a user clicks a button that will cause your program to start a multi-step task: with loops, calls to child methods, creating objects etc. If anything goes wrong, you want to abandon the entire task and wait for the next input. You don't want to stubbornly keep trying to complete the task; because you'll either just get more errors, or worse: later changes will do the wrong thing because earlier required steps did not complete successfully.
Easy Logging of Errors
As mentioned earlier, if you simply leave an exception alone, the error will "bubble up" to an outer exception handler. And in a standard GUI application, that will be the default Application exception handler.
In fact at this stage, doing nothing special: if an exception is raised in the middle of the button click task described earlier, the default exception handler will show an error message to the user. The only thing missing is the logging. Fortunately, Delphi makes it easy for you to intercept the default Application exception handler. So you can provide your own implementation, and log the error as you desire.
All you need to do is:
Write a method using the TExceptionEvent signature.
E.g. procedure MyHandleException(ASender: TObject; E: Exception);.
Then assign your custom handler using: Application.OnException := MyHandleException;
General Logging
Of course, this only covers logging of exceptions. If you want to do any other ad-hoc logging, you want to be able to reuse the code. This basically requires you to move your logging code into a separate unit that all your other units can call as needed.
So putting these things together you might do something as follows:
TLogger = class
public
procedure WriteToLog(S: string); //Performs actual logging of given string
procedure HandleException(ASender: TObject; E: Exception); //Calls WriteToLog
end;
Then you might set it up in your program unit as follows:
begin
Logger := TLogger.Create(...);
Application.OnException := Logger.HandleException;
Logger.WriteToLog('Custom exception handler has been assigned.');
//Rest of application startup code
end;
Final Thoughts
I mentioned that if an unexpected exception occurs, you want to abandon the current task. But what happens if you've already done some things that should now be undone as a result of the error. Then you would simply implement it as follows:
try
//Do something
except
//Undo
raise; //NOTE: more often than not, one should re-raise an
//exception so that callers are aware of the error.
end;
Note that this is the primary use for exception handlers. Yes they can also be used to swallow exceptions. But they should only swallow an exception if it has been truly resolved.
While Delphi is object-oriented language, it can deal with non-OO entities as well.
Think, your main form is property of which form ?
The answer is that main form is not the property of ANY other form, it is a global object. Same exactly thing should be done about your function.
unit Main;
interface uses .......;
type TMainForm = class (TForm)....
....
end;
Procedure LogError(const message: string);
implementation uses .....;
Procedure LogError(const message: string);
begin
...
end;
(**********)
procedure TMainForm.FormCreate(...);
begin
....
end;
...
end.
Now even better option would be to totally decouple LogError from ANY form at all.
Unit MyLogger;
interface uses ....; // but does not uses Main or any other form
procedure SetupLog(....);
// to initialize it and change any settings you may wish
// maybe you would choose to save messages to file
// or use Windows debug interface - OutputDebugString
// or to a network SysLog server
// or just WriteLn it to a text window
// or to some TMemo in a graphic window
// or to file AND to a memo - that also might make sense.
// just keep it extensible
Procedure LogWarning(const message: string);
Procedure LogError(const message: string);
implementation uses ...;
function GenericLoggingSink(.....);
begin
...
end;
Procedure LogError(const message: string);
begin
GenericLoggingSink( Log_Message_Type_Error, message, ... );
end;
Procedure LogWarning(const message: string);
begin
GenericLoggingSink( Log_Message_Type_Warning, message, ... );
end;
And your Main form should just use this unit and this function on the same terms as all other forms in your application.
As part three I suggest you think what you want from your logging procedures.
Actually doing this only makes sense for very simplistic logging needs. Very simplistic.
Otherwise just take any existing Delphi logging frameworks and utilize a lot of functionality implemented there. For example can you just log an object of any class you would write?
The only reason to make your own logging library is "my program is so simplistic, that it does need only the most primitive logging. It would be faster to improvise my own system, than to copy-paste library initialization and setup from examples to a ready-made libraries".
Log4Delphi and Log4D are well-known FLOSS libraries, though they look somewhat abandoned. Maybe there just is nothing left to be extended. Those are most old ones, but there also are some newer FLOSS libraries, an easy example being http://blog.synopse.info/post/2013/02/03/Log-to-the-console
If anything, you can read their texts and learn from them.
There are also commercial libraries like those listed at Is there a way to do normal logging with EureakLog? and Delphi itself is shipped with a limited version of CodeSite logging framework.
Delphi IDE enhancements like CnWizards and GExperts also do come with a simplified debug-oriented logging interface.
Google would bring you even more options if you'd dare :-)

Disable exception handling and let windows catch it?

I want to disable the exception catching by Delphi and let Windows catch it - making it produce a window like "AppName crashed. Debug , Send", add this to Application events, create a memory dump and so on.
By default, Delphi catches all the exception in TApplication.Run procedure... How can I avoid that without modifying Forms.pas?
You could add an OnException handler that re-raised the exception:
class procedure TMainForm.OnException(Sender: TObject; E: Exception);
begin
raise Exception(AcquireExceptionObject);
end;
initialization
Application.OnException := TMainForm.OnException;
I'm not sure why you would want to do this at all though. It's more normal to use a tool like madExcept or EurekaLog to show an error dialog that yields much more helpful information than the system dialog.
You can set JITEnable to '1' or higher (default is '0'). With '1', non native exceptions, with higher than '1', all exceptions will be handled by JIT or WER (depending on the system).
This may not be what you want though. With this solution any qualifying exception will be passed to the OS, it doesn't matter if they're handled in code or not. Clarification (run outside the debugger):
procedure TForm1.Button1Click(Sender: TObject);
begin
raise EAccessViolation.Create('access denied');
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
try
PInteger(0)^ := 0;
except
end;
end;
initialization
JITEnable := 1;
The first example is a native exception, it will be handled by the application exception handling mechanism when JITEnable is 1. But the second example will trigger JIT/WER.
Add your own handler. Application.OnException is probably what you want. Better than leaving it up to windows as well, as you get different behaviours depending on the environment. For instance if windows error reporting is on, it will ask the user if they want to send an error report to MS.
Like Mr Heffernan I recommend you look at something like EurekaLog.
AS. I agree with voices above that this wish is rather strange.
I also agree that practically hooking in TApplication.OnException would probably be enough ("if it looks like a duck...")
However if you truly want to make RTL oblivious to exceptions, there are ways too.
Exception handlers are plugin to low-level RTL, just like heap management, etc.
You can look at KOL (Key Objects Library).
In Delphi 5 times i managed to make 2KB-size DLL.
That required absense of many usualyl taken "for granted" features. Exception were among them.
To enable Exceptions in KOL's system RTL replacement, you had to make some $DEFINE's, and then the code to add exceptions support to IDE was unlocked.
I believe you can still get that modularized RTL version and grep for that $IfDef and see which code is replaced with which.
I believe there is fair chance you can undo that and make Windows avoid calling Delphi RTL over Exceptions.
I don't remember details, but i believe Delphi RTL Exception handler is just registered in Windows core as a callback. And you probably can de-register it (register nil callback).
I believe you can find it in stock RTL, but KOL's modularised RTL would just make it easier to search.

It is possible to resume execution from the point where an exception was raised?

There is an example which illustrates my question:
procedure Test;
begin
try
ShowMessage('1');
raise EWarning.Create(12345, 'Warning: something is happens!');
ShowMessage('2');
except
on E: EWarning do
if IWantToContinue(E.ErrorCode) then
E.SkipThisWarning // shows '1' then '2'
else
E.StopExecution; // shows '1'
end;
end;
function IWantToContinue(const ErrorCode: Integer): Boolean;
begin
//...
end;
I tried to use something like this:
asm
jmp ExceptAddr
end;
but it's wont' work...
Any ideas?
Thanks.
No, it is not possible:
There are two kinds of exceptions: logic exceptions that are raised by the programmer using the raise command, and external exceptions that are initiated by the CPU for various conditions: division by zero, stack overflow, access violation. For the first kind, the logic exceptions, there's nothing you can do because they're part of the application "flow". You can't mess with the flow of 3rd party code, you can't even mess with the flow of your own code.
External exceptions
Those are normally raised as a consequence of running a single CPU instruction, when that instruction fails. In Delphi those are made available as EExternal descendants. The list includes access violations, division by zero, stack overflow, privileged instruction and not-so-many others. Theoretically, for some of those exceptions, the causing condition of the exception could be removed and the single CPU instruction retried, allowing the original code to continue as if no error happened. For example SOME access violations might be "fixed" by actually mapping a page of RAM at the address where the error occurred.
There are provisions in the SEH (Structured Exception Handling) mechanism provided by Windows for dealing with such retry-able errors, and Delphi is using SEH under the hood. Unfortunately Delphi doesn't expose the required elements to make this easily accessible, so using them would be very difficult if not impossible. None the less, for particular types of EExternal errors, smart Delphinians might attempt writing custom SEH code and get things working. If that's the case, please ask a new question mentioning the particular type of error you're getting plus the steps you'd like to take to remove the error condition: you'll either get some working code or a customized explanation of why your idea would not work.
Logic exceptions initiated through the use of raise
Most exceptions fall into this category, because most code will check it's inputs before doing potentially dangerous low level stuff. For example, when trying to access an invalid index in a TList, the index would be checked and an invalid index exception raised before attempting to access the requested index. Without the check, accessing the invalid index would either return invalid data or raise an Access Violation. Both of those conditions would be very hard to track errors, so the invalid index exception is a very good thing. For the sake of this question, even if code were allowed to access an invalid index, causing an Access Violation, it would be impossible to "fix" the code and continue, because there's no way to guess what the correct index should be.
In other words, fixing "logic" exceptions doesn't work, shouldn't work, and it's insanely dangerous. If the code that raises the error is yours then you can simply restructure it to NOT raise exceptions for warnings. If that's not your code, then continuing the exception falls into the "insanely dangerous" category (not to mention it's technically not possible). When looking at already written code, ask yourself: would the code behave properly if the raise Exeption were replaced with ShowMessage? The answer should mostly be "NO, the code would fail anyway". For the very rare, very wrong case of 3rd party code that raises an exception for no good reason, you may ask for specific help on patching the code at run-time to NEVER raise the exception.
Here's what could be in some 3rd party code:
function ThirdPartyCode(A, B: Integer): Integer;
begin
if B = 0 then raise Exception.Create('Division by zero is not possible, you called ThirdPartyCode with B=0!');
Result := A div B;
end;
It should be obvious that continuing that code after the exception is not going to allow stuff to "self heal".
Third party code might also look like this:
procedure DoSomeStuff;
begin
if SomeCondition then
begin
// do useful stuff
end
else
raise Exception.Create('Ooops.');
end;
Where would that code "continue"? Quite obviously not the "do usefull stuff" part, unless the code is specifically designed that way.
Those were, of course, simple examples only scratching the surface. From a technical perspective, "continuing" after an exception as you're suggesting is a lot more difficult then jumping to the address of the error. Method calls use stack space to set up local variables. That space was released in the process of "rolling back" after the error, on the way to your exception handler. finally blocks were executed, possibly de-allocating resources where needed. Jumping back to the originating address would be very wrong, because the calling code no longer has what it expects on stack, it's local variables are no longer what they're ment to be.
If it's your code that's raising the exception
Your code can easily be fixed. Use something like this:
procedure Warning(const ErrorText:string);
begin
if not UserWantsToContinue(ErrorText) then
raise Exception.Create(ErrorText);
end;
// in your raising code, replace:
raise Exception.Create('Some Text');
// with:
Warning('Some Text');
AFAIK no. You have to restructure your code to something like
procedure Test;
begin
ShowMessage('1');
try
raise EWarning.Create(12345, 'Warning: something is happens!');
except
on E: EWarning do
if IWantToContinue(E.ErrorCode) then
// shows '1' then '2'
else
raise; // shows '1'
end;
ShowMessage('2');
end;
In C++, it is possible using an SEH __try/__except block whose __except expression evaluates to EXCEPTION_CONTINUE_EXECUTION.
In Delphi, it is not possible to use SEH directly, AFAIK.
BASICally, your code wont work because ExceptAddr is a function, not a variable. So, your code snippet changes as follows:
{$O-}
procedure TForm1.FormCreate(Sender: TObject);
begin
try
OutputDebugString('entering');
raise Exception.Create('Error Message');
OutputDebugString('leaving');
except on E: Exception do
begin
OutputDebugString(PChar(Format('ExceptAddr = %p', [ExceptAddr])));
asm
CALL ExceptAddr
JMP EAX
end;
end;
end;
end;
..you could nest multiple try-except and determine if continue inside each exception:
try
try
some code
except
ret := message('want to continue?');
if ret <> mrYes then
exit;
end;
some other code to perform when no exception or user choose to continue
except
etc..
end;

Resources