Delphi 2010 - EDirectoryNotFoundException when trying to save logs - delphi

I'm developing a program in Delphi 2010 that has to save the logs that are stored in a Tmemo. I'm trying to create a log file for everyday in which I append the logs from a memo.After I append the text I clear the content of the memo. So in the location of my app i want to create a folder named "loguri-mover_ftp" in which i want to store the log file. EX: log_mover-ftp_2-16-2015.txt
The code I use for this is:
If DirectoryExists(ExtractFilePath(Application.ExeName) + 'loguri-mover_ftp') then
begin
TFile.AppendAllText(ExtractFilePath(Application.ExeName) + 'loguri-mover_ftp\log_mover-ftp_' + datetostr(now) + '.txt',memo_loguri.lines.text, TEncoding.UTF8);
Memo_loguri.lines.text:='';
end
else
begin
CreateDir(ExtractFilePath(Application.ExeName) + 'loguri-mover_ftp');
TFile.AppendAllText(ExtractFilePath(Application.ExeName) + 'loguri-mover_ftp\log_mover-ftp_' + datetostr(now) + '.txt',memo_loguri.lines.text, TEncoding.UTF8);
Memo_loguri.lines.text:='';
end;
Because I'm interested in the stability of my application I've enabled the MadExcept debugger inside my app. After 2 hours 12 minutes i get the following error:
exception class : EDirectoryNotFoundException
exception message : The specified path was not found.
compiled with : Delphi 2010
program up time : 2 hours 12 minutes
madExcept version : 4.0.7
callstack crc : $bed2c7c0, $c58f696b, $05cb237f
count : 5
exception number : 1
disassembling:
[...]
005ce40a push eax
005ce40b call -$13ee30 ($48f5e0) ; SysUtils.TEncoding.GetUTF8
005ce410 mov ecx, eax
005ce412 pop eax
005ce413 pop edx
005ce414 > call -$1144ad ($4b9f6c) ; IOUtils.TFile.AppendAllText
005ce419 775 mov eax, [ebp+8]
005ce41c mov eax, [eax+$2a0]
005ce422 xor edx, edx
005ce424 mov ecx, [eax]
005ce426 call dword ptr [ecx+$2c]
[...]
What am I doing wrong?

The exception is raised by the call to AppendAllText. If you follow the source for that function you will find a call to InternalCheckFilePathParam, the implementation of which looks like this:
class procedure TFile.InternalCheckFilePathParam(const Path: string;
const FileExistsCheck: Boolean);
begin
if (Length(Path) >= MAX_PATH - TFile.FCMinFileNameLen) and
(not TPath.IsExtendedPrefixed(Path)) then
raise EPathTooLongException.CreateRes(#SPathTooLong);
if not TPath.HasPathValidColon(Path) then
raise ENotSupportedException.CreateRes(#SPathFormatNotSupported);
if Trim(Path) = '' then // DO NOT LOCALIZE
raise EArgumentException.CreateRes(#SInvalidCharsInPath);
if not TPath.HasValidPathChars(Path, False) then
raise EArgumentException.CreateRes(#SInvalidCharsInPath);
if not TDirectory.Exists(TPath.DoGetDirectoryName(TPath.DoGetFullPath(Path))) then
raise EDirectoryNotFoundException.CreateRes(#SPathNotFound);
if FileExistsCheck and (not Exists(Path)) then
raise EFileNotFoundException.CreateRes(#SFileNotFound);
end;
Now, Path is the first argument that you passed to AppendAllText. Since EDirectoryNotFoundException is being raised, we can conclude that the directory containing Path does not exist.
Of course, this seems odd give that you check for its existence and then create it. I think the mystery can be solved by looking at what datetostr(now) returns. You imagine that the date separator used is -. But what if the date separator is /? In that case the / will be interpreted as a path delimiter.
The solution is to specify the date separator explicitly by using the DateToStr overload that accepts a TFormatSettings parameter.
I also cannot ignore the duplication in your code. Please don't ever repeat magic strings the way you do. The code should look like this:
LogFileDir := TPath.Combine(ExtractFilePath(Application.ExeName), 'loguri-mover_ftp');
if not DirectoryExists(LogFileDir) then
ForceDirectories(LogFileDir);
DateStr := DateToStr(Now, ...); // you supply an appropriate TFormatSettings
LogFilePath := TPath.Combine(LogFileDir, log_mover-ftp_' + DateStr + '.txt');
TFile.AppendAllText(LogFilePath, memo_loguri.lines.text, TEncoding.UTF8);

Related

What apart from the "Compiling" options can change code gen in 64 bit?

Introduction
I encountered a problem with Currency in one of our applications. I was getting different results in Win32 and Win64. I found an article here that shows a similar problem but that one was fixed in XE6. The first thing I tried to do was create a MCVE to duplicate the problem. That's where the wheels fell off. What looks like the identical code in the MCVE produces a different result compared to the application. The generated code 64 bit is different. So my question morphed into why are they different and once I figure that out then I can create a suitable MCVE.
I have a method that is calculating a total. This method calls another method to get a value that needs to be added to the total. The method returns a single. I assign the single value to a variable and then add it to the total which is a Currency. In my main application the value for the total is used later on but adding that to the MCVE doesn't change the behavior. I made sure that the compiler options were the same.
In my main application, the result from the calculation is $2469.6001 in Win32 and 2469.6 in Win64 but I can't duplicate this in the MCVE. Everything on the Compiling options page was the same and optimizations were disabled.
Attempted MCVE
Here is the code for my attempted MCVE. This mimics the actions in the original application.
program Project4;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TTestClass = class
strict private
FMyCurrency: Currency;
function GetTheValue: Single;
public
procedure Calculate;
property MyCurrency: Currency read FMyCurrency write FMyCurrency;
end;
procedure TTestClass.Calculate;
var
myValue: Single;
begin
FMyCurrency := 0.0;
myValue := GetTheValue;
FMyCurrency := FMyCurrency + myValue;
end;
function TTestClass.GetTheValue: Single;
var
myValueExact: Int32;
begin
myValueExact := 1159354778; // 2469.60009765625;
Result := PSingle(#myValueExact)^;
end;
var
testClass: TTestClass;
begin
testClass := TTestClass.Create;
try
testClass.Calculate;
WriteLn(CurrToStr(testClass.MyCurrency));
ReadLn;
finally
testClass.Free;
end;
end.
This code generates the following assembler for the last two lines of TTestClass.Calculate:
Project4.dpr.25: myValue := GetTheValue;
00000000004242A8 488B4D40 mov rcx,[rbp+$40]
00000000004242AC E83F000000 call TTestClass.GetTheValue
00000000004242B1 F30F11452C movss dword ptr [rbp+$2c],xmm0
Project4.dpr.26: FMyCurrency := FMyCurrency + myValue;
00000000004242B6 488B4540 mov rax,[rbp+$40]
00000000004242BA 488B4D40 mov rcx,[rbp+$40]
00000000004242BE F2480F2A4108 cvtsi2sd xmm0,qword ptr [rcx+$08]
00000000004242C4 F3480F5A4D2C cvtss2sd xmm1,qword ptr [rbp+$2c]
00000000004242CA F20F590D16000000 mulsd xmm1,qword ptr [rel $00000016]
00000000004242D2 F20F58C1 addsd xmm0,xmm1
00000000004242D6 F2480F2DC8 cvtsd2si rcx,xmm0
00000000004242DB 48894808 mov [rax+$08],rcx
Main Application
This is an extract from the main application. It's difficult to give more information but I don't think that will change the nature of the question. In this class, FBulkTotal is declared as a Currency that is strict private. UpdateTotals is public.
procedure TMainApplicationClass.UpdateTotals(aMyObject: TMyObject);
var
bulkTotal: Single;
begin
..
bulkTotal := grouping.GetTotal(aMyObject, Self);
FBulkTotal := FBulkTotal + bulkTotal;
..
end;
The generated code for these two lines is:
TheCodeUnit.pas.7357: bulkTotal := grouping.GetTotal(aMyObject, Self);
0000000006DB0804 488B4D68 mov rcx,[rbp+$68]
0000000006DB0808 488B9598000000 mov rdx,[rbp+$00000098]
0000000006DB080F 4C8B8590000000 mov r8,[rbp+$00000090]
0000000006DB0816 E8551C0100 call grouping.GetTotal
0000000006DB081B F30F114564 movss dword ptr [rbp+$64],xmm0
TheCodeUnit.pas.7358: FBulkTotal := FBulkTotal + bulkTotal;
0000000006DB0820 488B8590000000 mov rax,[rbp+$00000090]
0000000006DB0827 488B8D90000000 mov rcx,[rbp+$00000090]
0000000006DB082E F3480F2A8128010000 cvtsi2ss xmm0,qword ptr [rcx+$00000128]
0000000006DB0837 F30F104D64 movss xmm1,dword ptr [rbp+$64]
0000000006DB083C F30F590D54020000 mulss xmm1,dword ptr [rel $00000254]
0000000006DB0844 F30F58C1 addss xmm0,xmm1
0000000006DB0848 F3480F2DC8 cvtss2si rcx,xmm0
0000000006DB084D 48898828010000 mov [rax+$00000128],rcx
What's strange is that the generated code is different. The MCVE has a cvtsi2sd followed by a cvtss2sd but that main application uses a movss in place of the cvtss2sd when copying the contents of the single value into the xmm1 register. I pretty sure that is what is causing the different result but without being able to create a MCVE, I can't even confirm that it is a problem with the compiler.
Question
My question is what can cause these differences in code generation? I assumed that the optimizations could do this type of thing but I made sure those were the same.
You should not be using any floating point type values when dealing with currency.
I recommend you watch Floating Point Numbers video from Computerphile where he explains of how floating point values are handled by computers and why they should not be used when handling currency.
https://www.youtube.com/watch?v=PZRI1IfStY0

Is the compiler treatment of implicit interface variables documented?

I asked a similar question about implicit interface variables not so long ago.
The source of this question was a bug in my code due to me not being aware of the existence of an implicit interface variable created by the compiler. This variable was finalized when the procedure that owned it finished. This in turn caused a bug due to the lifetime of the variable being longer than I had anticipated.
Now, I have a simple project to illustrate some interesting behaviour from the compiler:
program ImplicitInterfaceLocals;
{$APPTYPE CONSOLE}
uses
Classes;
function Create: IInterface;
begin
Result := TInterfacedObject.Create;
end;
procedure StoreToLocal;
var
I: IInterface;
begin
I := Create;
end;
procedure StoreViaPointerToLocal;
var
I: IInterface;
P: ^IInterface;
begin
P := #I;
P^ := Create;
end;
begin
StoreToLocal;
StoreViaPointerToLocal;
end.
StoreToLocal is compiled just as you would imagine. The local variable I, the function's result, is passed as an implicit var parameter to Create. The tidy up for StoreToLocal results in a single call to IntfClear. No surprises there.
However, StoreViaPointerToLocal is treated differently. The compiler creates an implicit local variable which it passes to Create. When Create returns, the assignment to P^ is performed. This leaves the routine with two local variables holding references to the interface. The tidy up for StoreViaPointerToLocal results in two calls to IntfClear.
The compiled code for StoreViaPointerToLocal is like this:
ImplicitInterfaceLocals.dpr.24: begin
00435C50 55 push ebp
00435C51 8BEC mov ebp,esp
00435C53 6A00 push $00
00435C55 6A00 push $00
00435C57 6A00 push $00
00435C59 33C0 xor eax,eax
00435C5B 55 push ebp
00435C5C 689E5C4300 push $00435c9e
00435C61 64FF30 push dword ptr fs:[eax]
00435C64 648920 mov fs:[eax],esp
ImplicitInterfaceLocals.dpr.25: P := #I;
00435C67 8D45FC lea eax,[ebp-$04]
00435C6A 8945F8 mov [ebp-$08],eax
ImplicitInterfaceLocals.dpr.26: P^ := Create;
00435C6D 8D45F4 lea eax,[ebp-$0c]
00435C70 E873FFFFFF call Create
00435C75 8B55F4 mov edx,[ebp-$0c]
00435C78 8B45F8 mov eax,[ebp-$08]
00435C7B E81032FDFF call #IntfCopy
ImplicitInterfaceLocals.dpr.27: end;
00435C80 33C0 xor eax,eax
00435C82 5A pop edx
00435C83 59 pop ecx
00435C84 59 pop ecx
00435C85 648910 mov fs:[eax],edx
00435C88 68A55C4300 push $00435ca5
00435C8D 8D45F4 lea eax,[ebp-$0c]
00435C90 E8E331FDFF call #IntfClear
00435C95 8D45FC lea eax,[ebp-$04]
00435C98 E8DB31FDFF call #IntfClear
00435C9D C3 ret
I can guess as to why the compiler is doing this. When it can prove that assigning to the result variable will not raise an exception (i.e. if the variable is a local) then it uses the result variable directly. Otherwise it uses an implicit local and copies the interface once the function has returned thus ensuring that we don't leak the reference in case of an exception.
But I cannot find any statement of this in the documentation. It matters because interface lifetime is important and as a programmer you need to be able to influence it on occasion.
So, does anybody know if there is any documentation of this behaviour? If not does anyone have any more knowledge of it? How are instance fields handled, I have not checked that yet. Of course I could try it all out for myself but I'm looking for a more formal statement and always prefer to avoid relying on implementation detail worked out by trial and error.
Update 1
To answer Remy's question, it mattered to me when I needed to finalize the object behind the interface before carrying out another finalization.
begin
AcquirePythonGIL;
try
PyObject := CreatePythonObject;
try
//do stuff with PyObject
finally
Finalize(PyObject);
end;
finally
ReleasePythonGIL;
end;
end;
As written like this it is fine. But in the real code I had a second implicit local which was finalized after the GIL was released and that bombed. I solved the problem by extracting the code inside the Acquire/Release GIL into a separate method and thus narrowed the scope of the interface variable.
If there is any documentation of this behavior, it will probably be in the area of compiler production of temporary variables to hold intermediate results when passing function results as parameters. Consider this code:
procedure UseInterface(foo: IInterface);
begin
end;
procedure Test()
begin
UseInterface(Create());
end;
The compiler has to create an implicit temp variable to hold the result of Create as it is passed into UseInterface, to make sure that the interface has a lifetime >= the lifetime of the UseInterface call. That implicit temp variable will be disposed at the end of the procedure that owns it, in this case at the end of the Test() procedure.
It's possible that your pointer assignment case may fall into the same bucket as passing intermediate interface values as function parameters, since the compiler can't "see" where the value is going.
I recall there have been a few bugs in this area over the years. Long ago (D3? D4?), the compiler didn't reference count the intermediate value at all. It worked most of the time, but got into trouble in parameter alias situations. Once that was addressed there was a follow up regarding const params, I believe. There was always a desire to move disposal of the intermediate value interface up to as soon as possible after the statement in which it was needed, but I don't think that ever got implemented in the Win32 optimizer because the compiler just wasn't set up for handling disposal at statement or block granularity.
You can not guarantee that compiler will not decide to create a temporal invisible variable.
And even if you do, the turned off optimization (or even stack frames?) may mess up your perfectly checked code.
And even if you manage to review your code under all possible combinations of project options - compiling your code under something like Lazarus or even new Delphi version will bring hell back.
A best bet would be to use "internal variables can not outlive routine" rule. We usually do not know, if compiler would create some internal variables or not, but we do know, that any such variables (if created) would be finalized when routine exists.
Therefore, if you have code like this:
// 1. Some code which may (or may not) create invisible variables
// 2. Some code which requires release of reference-counted data
E.g.:
Lib := LoadLibrary(Lib, 'xyz');
try
// Create interface
P := GetProcAddress(Lib, 'xyz');
I := P;
// Work with interface
finally
// Something that requires all interfaces to be released
FreeLibrary(Lib); // <- May be not OK
end;
Then you should just wrap "Work with interface" block into subroutine:
procedure Work(const Lib: HModule);
begin
// Create interface
P := GetProcAddress(Lib, 'xyz');
I := P;
// Work with interface
end; // <- Releases hidden variables (if any exist)
Lib := LoadLibrary(Lib, 'xyz');
try
Work(Lib);
finally
// Something that requires all interfaces to be released
FreeLibrary(Lib); // <- OK!
end;
It is a simple, but effective rule.

Is there an ANSI version for Copy?

Under Delphi XE, is there an ANSI version for Copy?
I am using Copy a lot to copy pieces of a ANSI strings.
Altar the Copy function in Delphi is a intrinsic function this means which is handled by the compiler rather than the run-time library. depending of the parameters passed this function call the LStrCopy or a UStrCopy internal functions
check this sample :
{$APPTYPE CONSOLE}
uses
SysUtils;
Var
s : AnsiString;
u : string;
begin
try
s:='this is a ansi string';
s:= Copy(s,1,5);
Writeln(s);
u:='this is a unicode string';
u:= Copy(u,1,5);
Writeln(u);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
Now check the assembly code
Project91.dpr.12: s:='this is a ansi string';
004111DC B8787E4100 mov eax,$00417e78
004111E1 BA04134100 mov edx,$00411304
004111E6 E8314FFFFF call #LStrAsg
Project91.dpr.13: s:= Copy(s,1,5);
004111EB 68787E4100 push $00417e78
004111F0 B905000000 mov ecx,$00000005
004111F5 BA01000000 mov edx,$00000001
004111FA A1787E4100 mov eax,[$00417e78]
004111FF E8A050FFFF call #LStrCopy //call the ansi version of copy
Project91.dpr.14: Writeln(s);
00411204 A1EC2C4100 mov eax,[$00412cec]
00411209 8B15787E4100 mov edx,[$00417e78]
0041120F E84033FFFF call #Write0LString
00411214 E8DF33FFFF call #WriteLn
00411219 E8D22AFFFF call #_IOTest
Project91.dpr.15: u:='this is a unicode string';
0041121E B87C7E4100 mov eax,$00417e7c
00411223 BA28134100 mov edx,$00411328
00411228 E8534EFFFF call #UStrAsg
Project91.dpr.16: u:= Copy(u,1,5);
0041122D 687C7E4100 push $00417e7c
00411232 B905000000 mov ecx,$00000005
00411237 BA01000000 mov edx,$00000001
0041123C A17C7E4100 mov eax,[$00417e7c]
00411241 E8C654FFFF call #UStrCopy //call the unicode version of copy
Project91.dpr.17: Writeln(u);
00411246 A1EC2C4100 mov eax,[$00412cec]
Copy is a "compiler magic" routine, it is handled intrinsically by the compiler depending on what parameters you pass it (ANSI string, string, or dynamic array). You can just use Copy; it will work correctly with ANSI strings.
I have the same problem, see this code:
const
TheStart=13;
TheEnd=69;
type
TMyFileField: Array[TheStart..TheEnd] of Char; // This is a simplification of a field type on a file
procedure WriteWideStringToArrayOfChars(TheLiteral:WideString);
var
MyFileField:TMyFileField; // This is a simplification, it is really a Field inside a File
MyIndex:Integer;
begin
for MyIndex:=1 to Max(Length(TheLiteral),1+TheEnd-TheStart)
do begin // Will copy as many charactes as possible from TheLiteral to MyFileField
MyFileField[MyIndex]:=Copy(TheLiteral,MyIndex,1)[1]; // This gives Copile Error: Incompatible types 'Char' and 'WideChar'
end;
end;
The problem is that the WideString must be saved onto am Array of Char inside a file. So mix types must be done... and so, some loose of Unicode chars will occur, no way to avoid it.
The wanted: The compiler can compile it.
Solution1: Convert WideString to String prior to call Copy, or inside Copy.
Solution2: Convert WideChar to Char prior to assing.
Here are both solutions (remember some unicode chars could get lost)...
Solution1:
MyFileField[MyIndex]:=Copy(UTF8Encode(TheLiteral),MyIndex,1)[1]; // Note: Unicode chars will not get lost, but converted, so beware of accent vocals, etc...
or
MyFileField[MyIndex]:=Copy(String(TheLiteral),MyIndex,1)[1]; // Note: Unicode chars will get lost, they will be converted to '?'
Solution2:
MyFileField[MyIndex]:=Char(Copy(TheLiteral,MyIndex,1)[1]); // Note: Unicode chars will get lost, they will be converted to '?'
If anyone knows anthing better i would be glad to know.
I personally use Copy(String(Literal),Start,NumberOfChars) since normal accent letters are conserved and more important, length...
Example: Length(String('BlaBlaBlá')) -> 9
Example: Length(UTF8Encode('BlaBlaBlá')) -> More than 9 since the last 'á' is converted to multiple chars, etc...
Hope this can help someone!
I did my own function. It could be useful and get right results instead of Copy(string(ansistr), i, l) on the Linux platform:
function AnsiCopy(const s: ansistring; StartIndex, Lenght: integer): ansistring;
begin
SetLength(Result, Lenght);
Move(s[StartIndex], Result[1], Lenght);
end;

Delphi TPrinters.GetPrinters call hangs

I have an app that has returned an error report. The app is written in Delphi 2006 and hangs during startup. The MadExcept main thread stack is shown below. I suspect there is no default printer but I can't replicate the fault here.
Anyone seen this problem?
Initialization part of unit WWPrintToPrinterOrPDFRoutines
initialization
PagesRangeStartPage := 1 ;
PagesRangeEndPage := 999 ;
PrintRange := prAll ;
PrintCopies := 1 ;
PrintCollate := false ;
InitialPrintPaperName := 'A4' ;
if (Printer.Printers.Count = 0) then // <--------- this causes the hang
begin
InitialPrintOrientation := Printers.poPortrait ;
end
else
begin
InitialPrintOrientation := GetDefaultPrinterOrientation ;
InitialPrintPaperName := GetDefaultPrinterPaperName ;
end ;
CurrentPreviewPage := 1 ;
NDRMemoryStream := TMemoryStream.Create ;
or disassembled:
WWPrintToPrinterOrPDFRoutines.pas.682: PagesRangeStartPage := 1 ;
007C4404 C705EC8B81000100 mov [$00818bec],$00000001
WWPrintToPrinterOrPDFRoutines.pas.683: PagesRangeEndPage := 999 ;
007C440E C705F08B8100E703 mov [$00818bf0],$000003e7
WWPrintToPrinterOrPDFRoutines.pas.684: PrintRange := prAll ;
007C4418 C605F48B810001 mov byte ptr [$00818bf4],$01
WWPrintToPrinterOrPDFRoutines.pas.685: PrintCopies := 1 ;
007C441F C705F88B81000100 mov [$00818bf8],$00000001
WWPrintToPrinterOrPDFRoutines.pas.686: PrintCollate := false ;
007C4429 C605FC8B810000 mov byte ptr [$00818bfc],$00
WWPrintToPrinterOrPDFRoutines.pas.687: InitialPrintPaperName := 'A4' ;
007C4430 B8288C8100 mov eax,$00818c28
007C4435 BAC0447C00 mov edx,$007c44c0
007C443A E82D1AC4FF call #LStrAsg
WWPrintToPrinterOrPDFRoutines.pas.689: if (Printer.Printers.Count = 0) then
007C443F E8B0BCCDFF call Printer
007C4444 E89FB7CDFF call TPrinter.GetPrinters <----- HANG OCCURS HERE
I don't think there is anything wrong with your program or anything you could change to make this not hang. Something is wrong on the OS level with that system.
That NdrClientCall2 function is part of the Remote Procedure Call Network Data Representation Engine which is used for making RPC and DCOM calls.
NtConnectPort is a function to connect a port object (that's a fundamental kernel object, like e.g. a mutex or a file handle). Ports are used by windows at the lowest level for LPCs.
A call to NtConnectPort will block until the server called NtCompleteConnectPort (there is no timeout handling for calls to NtConnectPort).
So your problem is that winspool.drv tries to establish an LPC connection to another process on the same machine (my guess would be spoolsv.exe, the printer spooler service, but it's impossible to tell from the information provided) and this other process has created a port (NtCreatePort) but has either not called NtListenPort on it, or when NtListenPort returns does not call NtAcceptConnectPort and NtCompleteConnectPort on it. Which prevents the call to NtConnectPort in your process from every returning.
So the real problem is outside of your process, in whatever process the other side of the port belongs to.

How to get current method's name in Delphi 7?

Is there any way to know the name of a method I'm currently in?
So that:
procedure TMyObject.SomeMethod();
begin
Writeln('my name is: ' + <hocus pocus>);
end;
would produce this output:
my name is: SomeMethod
JCL is free and has functions for that. It does depend on how well a stack trace can be made and how much debug information is present.
JclDebug.pas
function FileByLevel(const Level: Integer = 0): string;
function ModuleByLevel(const Level: Integer = 0): string;
function ProcByLevel(const Level: Integer = 0): string;
function LineByLevel(const Level: Integer = 0): Integer;
See also our TSynMapFile class.
It is able to load a .map file, and compress it into an optimized binary format. It will be much smaller than the .map itself (e.g. 900 KB .map -> 70 KB .mab). This .mab can be easily embedded within the exe. It is therefore smaller than the format used by JCL or MadExcept, and also smaller than the information embedded at compile time by Delphi.
You'll use it as such:
Map := TSynMapFile.Create; // or specify an exe name
try
i := Map.FindSymbol(SymbolAddr);
if i>=0 then
writeln(Map.Symbols[i].Name);
// or for your point:
writeln(Map.FindLocation(Addr)); // e.g. 'SynSelfTests.TestPeopleProc (784)'
finally
Map.Free;
end;
For instance, here is how it is used from our logging classes.
procedure TSynLog.Log(Level: TSynLogInfo);
var aCaller: PtrUInt;
begin
if (self<>nil) and (Level in fFamily.fLevel) then begin
LogHeaderLock(Level);
asm
mov eax,[ebp+4] // retrieve caller EIP from push ebp; mov ebp,esp
sub eax,5 // ignore call TSynLog.Enter op codes
mov aCaller,eax
end;
TSynMapFile.Log(fWriter,aCaller); // here it will call TSynMapFile for the current exe
LogTrailerUnLock(Level);
end;
end;
This method is able to retrieve the caller's address, and log its unit name, method name and line number.
Note/edit: the source code of the mORMot log unit is SynLog.pas. The updated documentation is reacheable at this URI.
If you have EurekaLog:
uses
EDebugInfo;
procedure TMyObject.SomeMethod();
begin
Writeln('my name is: ' + __FUNCTION__);
end;
There is also __FILE__, __MODULE__, __UNIT__, __LINE__, as well as a generic GetLocationInfoStr function.
However:
It will only work if you are compiling with some debug information (and the corresponding debug info provider is enabled):
EurekaLog has its own debug info format, which can be optionally compressed (which is actually not recomended, as you will spend more memory and CPU).
It also supports JCL/JEDI, Synopse/MAB, as well as .map, .tds/TD32, .dbg, .pdb.
It is NOT a constant. The name will be looked up dynamically, so it have some run-time costs.

Resources