Author: Ezra Hoch
The ASSERT function shows you the line number and unit name from which it was
launched. How can you get this functionality for different useages in your code
Answer:
Any information in this article has been checked on Delphi5 and Delphi 6 only (on
Pentium II processor). It might not work on any other configuration !
First, we need to understand how the ASSERT fucntion works. As you've probably
noticed, when an assertion occures, the error raised includes the line number and
unit name. How does this happen? Where does the ASSERT function get that info from?
The answer is very simple. From Delphi's compiler. When Delphi compiles your code
it addes a few lines in assembler code before the call to ASSERT. These assembler
instructions contain the extra information the ASSERT function needs.
That is, the line number from which it was called, and the unit's name. But what if
you want to have that info? How can you retreive the line number and unit name of a
specific statment in your code? The answer is : you can't (at least as far as I
know), at least not strait forwardly, but you can get it in some tricky way.
To solve this, we'll use Delphi's compiler. Since the compiler adds the extra
information we're looking for before an ASSERT function, we'll put an ASSERT
function in our code, and have the compiler add the needed information. Now we need
to find a way to avoid the ASSERT function call (sinc we don't want a simple
assertion error to happen). In order to do that, we'll add some assembler code to
skip the ASSERT function. You might wonder why to put the ASSERT fucntion in the
first place, if we're going to skip it in any case. The answer is, in order to read
the extra information (line number and unit name) we have to let the compiler add
it to our code, and that can be done only by adding the ASSERT function.
After skipping the ASSERT function, we'll read the information added by the
compiler (using assembler) and put it in some global variables. Afterwards you can
do what ever you wish with that info.
Here is the needed code :
1 var2 // Global variables to hold the result3 GLineNumber: Integer;
4 GError, GUnitName: string;
5 6 asm
7 8 // Save EAX and EBX cause we're going to change them9 push eax
10 push ebx
11 // The following 3 lines are in order to get the value of EIP12 // "call" pushes EIP to the stack13 call @Temp
14 @Temp:
15 // now we POP EIP into EBX16 pop ebx
17 // Add to EBX the value needed inorder to skip the ASSERT function18 add ebx, $1A
19 // Skip the ASSERT function by jumping to the code after it20 jmp ebx
21 end;
22 ASSERT(False, 'Your Error Message Here');
23 asm24 // Make EBX point to the line number value that the compiler inserted25 sub ebx, $1326 // Read that value into EAX27 mov eax, [ebx]
28 // Put the line number into GLineNumber29 mov GLineNumber, eax
30 31 // Same as above but for GUnitName32 add ebx, 533 mov eax, [ebx]
34 mov GUnitName, eax
35 36 // Same as above but for GError (the assertion error message)37 add ebx, 538 mov eax, [ebx]
39 mov GError, eax
40 41 // Restore the values of EBX and EAX42 pop ebx
43 pop eax
44 end;
Let's look back again on what this code does.
Delphi's compiler adds some info before any ASSERT function it finds in the code.
We add a call to ASSERT to make the compiler add the wanted information
We add assmbler code to skip the call to ASSERT
We add code (after the call to ASSERT) that reads to information that the compiler
added.
We do what ever we wish with this information, for example - raise a better
excpetion.
Note :
1) You might think this is to much code for such a simple task. Inorder to make
this code shorter, you can but the assmbler code in a file, and use the {$I}
include compiler directive. That way the code will look like this :
45 {$I PreAssert.INC}46 ASSERT(False, 'YourMessage');
47 {$I PostAssert.INC}
2) You might want to override the assertion handling method in System.Pas instead
of all of this, but then you won't be able to use regular ASSERT functions when
they are needed.
3) This code is not meant to replace ASSERT. It just uses ASSERT inorder to
retrieve the line number and unit name. This code is meant to get that information
for a specific loacation in your code.
4) You must pass "False" as the first parameter of the ASSERT function you write. If you pass "True" then the compiler completly ignores the function, and if you pass a condition (eg. i = 5) the compiler will generate more code then expected, and the assembler instructions that I've provided won't work properly.