Get functionname from the functionpointer? - delphi

I have a pointer to functions like this.
TTestEvent = function(): Boolean;
procedure ExecuteTest(aTest: TTestEvent; aType: String);
begin
if aTest then
NotifyLog(aType + ' success')
else
TestError(aType + ' failed');
end;
// Call the test
procedure TestAll;
begin
ExecuteTest(LoadParcels, 'LoadParcel');
end;
But it would be even nicer to extract the name of the function from the functionpointer aTest.
So instead of
aType + ' success'
I want something like
ExtractName(aTest) + ' success'
Can this be done in Delphi 2007 ?

If you use some of our Open Source classes, you'll be able to find the name of any symbol.
You'll have to create a .map file when building your executable, by setting "Detailed map" in your projects option.
You can then deliver the .map with the .exe, or compress the .map into our proprietary .mab format, which can be appended to the .exe. The .mab format is just a lot more efficient than .zip or other for this task: it is about 10 times smaller than the original .map file (that is, much smaller than JCLDebug or MaxExpect offers, and very much smaller than using the standard "Remote debugging symbol" embedding project option).
Then you can use the TSynMapFile class to retrieve debugging information from the .map file, or the information embedded into the .exe:
function ExtractName(aSymbolAddress: pointer): string;
var i: integer;
begin
with TSynMapFile.Create do // no name supplied -> will read from .exe
try
i := FindSymbol(aSymbolAddress);
if i>=0 then
result := Symbols[i].Name else
result := '';
finally
Free;
end;
end;
It will work for function names, but also any other symbols, like methods or global variables.
See this blog article about the class. And note that even if it is used by our mORMot framework, or its logging features, you just do not need to use the whole framework (just SynCommons.pas and SynLZ.pas units). See the Map2Mab.dpr program in "SQLite3\Samples\11 - Exception logging" sub folder to embed .map file content into an .exe.

You cannot do this with built-in features. In order to get a function name from an address you need to know the map of the executable. This is not part of an executable unless you take steps to add it.
Debugging tools like JclDebug and madExcept offer the functionality you are looking for.

You may implement a registration mechanism based on a Dictionary with
the function pointer as a Key and
the function name as a Value.
ExtractName would be a method of the dictionary.
Don't forget to make it thread safe if need be.

Related

Is there a delphi equivalent of C's __FILE__ or __LINE__ preprocessor directives [duplicate]

is it possible to get source line number at runtime in Delphi?
I know JCL debug, but I want to avoid to use it. Also Assert is not exactly what I want. I would like to get something like this, where GetLineNumber would get the source line number. Is it possible to do it without MAP files (anyway will MAP file be generated when I use Assert) ? Is there any example ?
function GetLineNumber: integer;
begin
???
end;
procedure ThisWouldBeGreat;
begin
if not SomeCondition then
LogMemo.Lines.Add('Error on line: ' + IntToStr(GetLineNumber));
end;
procedure ThatsWhatIWont;
begin
Assert(not SomeCondition, 'Error');
end;
Thank you
You can indeed use Assert for this. Write a procedure that matches the signature dictated by the TAssertErrorProc type, and then do whatever you want there. To preserve the expected behavior, you should probably call the original handler after you're finished.
procedure MichaelAssertProc(const Message, Filename: string;
LineNumber: Integer; ErrorAddr: Pointer);
begin
LogMessage(...);
SysUtils.AssertErrorHandler(Message, Filename, LineNumber, ErrorAddr);
end;
Assign that procedure to System.AssertErrorProc sometime while your program starts up.
AssertErrorProc := MichaelAssertProc;
For our logging and exception tracing classes, we made a .map parser and reader.
A .map can be parsed into a binary compressed version (.mab proprietary format), which is much smaller than the original .map. For instance, a 900 KB .map file is compressed into a 70 KB .mab file - this is a much higher compression than zip.
This .mab content can be appended to the .exe, without any difference at execution, or for the end-user.
Then you can use our logging classes, or directly the .map/.mab reader class, TSynMapFile.
You have the following methods at hand:
/// retrieve a .map file content, to be used e.g. with TSynLog to provide
// additional debugging information
// - original .map content can be saved as .mab file in a more optimized format
TSynMapFile = class
public
/// get the available debugging information
// - will first search for a .map file in the .exe directory: if found,
// will be read to retrieve all necessary debugging information - a .mab
// file will be also created in the same directory (if MabCreate is TRUE)
// - if .map is not not available, will search for the .mab file in the
// .exe directory
// - if no .mab is available, will search for a .mab appended to the exe
// - if nothing is available, will log as hexadecimal pointers, without
// debugging information
// - if aExeName is not specified, will use the current process executable
constructor Create(const aExeName: TFileName=''; MabCreate: boolean=true);
/// save all debugging information in the .mab custom binary format
// - if no file name is specified, it will be saved as ExeName.mab
// - this file content can be appended to the executable via SaveToExe method
// - this function returns the created file name
function SaveToFile(const aFileName: TFileName=''): TFileName;
/// save all debugging informat in our custom binary format
procedure SaveToStream(aStream: TStream);
/// append all debugging information to an executable
// - the executable name must be specified, because it's impossible to
// write to the executable of a running process
procedure SaveToExe(const aExeName: TFileName);
/// add some debugging information according to the specified memory address
// - will create a global TSynMapFile instance for the current process, if
// necessary
// - if no debugging information is available (.map or .mab), will write
// the address as hexadecimal
class procedure Log(W: TTextWriter; Addr: PtrUInt);
/// retrieve a symbol according to an absolute code address
function FindSymbol(aAddr: cardinal): integer;
/// retrieve an unit and source line, according to an absolute code address
function FindUnit(aAddr: cardinal; out LineNumber: integer): integer;
/// return the symbol location according to the supplied absolute address
// - i.e. unit name, symbol name and line number (if any), as plain text
// - returns '' if no match found
function FindLocation(aAddr: Cardinal): RawUTF8;
/// all symbols associated to the executable
property Symbols: TSynMapSymbolDynArray read fSymbol;
/// all units, including line numbers, associated to the executable
property Units: TSynMapUnitDynArray read fUnit;
published
/// the associated file name
property FileName: TFileName read fMapFile;
/// equals true if a .map or .mab debugging information has been loaded
property HasDebugInfo: boolean read fHasDebugInfo;
end;
Thanks to this class, in just one unit, you'll have all the source code line of any location.
Open source, and working with Delphi 5 up to XE (note that the .map format changed a little bit from old to newer versions - our class try to handle it).
Note: This answer addresses the following question.
Is it possible to get source line number at runtime in Delphi?
You can't do this without a map file or something equivalent. The process of compilation leaves the source code behind.
It's not practical to litter your source code with pre-emptive checks for errors. What's more, doing so will only give you very limited information for a very limited number of faults in your code. Generally, if you can anticipate an error, you won't get it wrong. It's the errors that you don't anticipate that make it into production code.
Really you are best off using madExcept, EurekaLog or JclDebug.
var LineNumber: Integer;
procedure MyAssert(const M, F: string; L: Integer; E: Pointer);
begin
LineNumber := L;
end;
procedure TForm1.Button1Click(Sender: TObject);
var I: Integer;
S: TAssertErrorProc;
begin
I := 0;
S := AssertErrorProc;
AssertErrorProc := MyAssert;
try
Assert(I <> 0);
finally
AssertErrorProc := S;
end;
showmessage(IntToStr(LineNumber));
end;
I created my own solution for catching line numbers by assertion.
{$IFDEF TRACELOG}
try
assert(0=1,'');
except on E : Exception do
tracelog(E.Message);
end;
{$ENDIF}
where tracelog() is:
procedure TraceLog(LogMessage: WideString);
var
tfile : TextFile;
logTime : WideString;
begin
logTime := formatDateTime('YYYY-MM-DD HH:NN:SS.zzz',Now);
logMessage := Copy(LogMessage,pos(', line ',LogMessage)+7,pos(')',LogMessage) - pos(', line ',LogMessage) - 7);
Assign(tfile,SrcDir+'data\TraceLog.txt');
if FileExists(SrcDir+'data\TraceLog.txt') then Append(tfile) else Rewrite(tfile);
Writeln(tfile,'{' + logTime + '} [' + LogMessage + ']');
CloseFile(tfile);
end;
and you can Enable/Disable this with Debugger flag
{$DEFINE TRACELOG}
I hope that helps you. I found it a great solution for debuging and tracing linenumbers, let me know if it is helpful.

Does Delphi have a pre-processor macro indicating the current file and line number? [duplicate]

is it possible to get source line number at runtime in Delphi?
I know JCL debug, but I want to avoid to use it. Also Assert is not exactly what I want. I would like to get something like this, where GetLineNumber would get the source line number. Is it possible to do it without MAP files (anyway will MAP file be generated when I use Assert) ? Is there any example ?
function GetLineNumber: integer;
begin
???
end;
procedure ThisWouldBeGreat;
begin
if not SomeCondition then
LogMemo.Lines.Add('Error on line: ' + IntToStr(GetLineNumber));
end;
procedure ThatsWhatIWont;
begin
Assert(not SomeCondition, 'Error');
end;
Thank you
You can indeed use Assert for this. Write a procedure that matches the signature dictated by the TAssertErrorProc type, and then do whatever you want there. To preserve the expected behavior, you should probably call the original handler after you're finished.
procedure MichaelAssertProc(const Message, Filename: string;
LineNumber: Integer; ErrorAddr: Pointer);
begin
LogMessage(...);
SysUtils.AssertErrorHandler(Message, Filename, LineNumber, ErrorAddr);
end;
Assign that procedure to System.AssertErrorProc sometime while your program starts up.
AssertErrorProc := MichaelAssertProc;
For our logging and exception tracing classes, we made a .map parser and reader.
A .map can be parsed into a binary compressed version (.mab proprietary format), which is much smaller than the original .map. For instance, a 900 KB .map file is compressed into a 70 KB .mab file - this is a much higher compression than zip.
This .mab content can be appended to the .exe, without any difference at execution, or for the end-user.
Then you can use our logging classes, or directly the .map/.mab reader class, TSynMapFile.
You have the following methods at hand:
/// retrieve a .map file content, to be used e.g. with TSynLog to provide
// additional debugging information
// - original .map content can be saved as .mab file in a more optimized format
TSynMapFile = class
public
/// get the available debugging information
// - will first search for a .map file in the .exe directory: if found,
// will be read to retrieve all necessary debugging information - a .mab
// file will be also created in the same directory (if MabCreate is TRUE)
// - if .map is not not available, will search for the .mab file in the
// .exe directory
// - if no .mab is available, will search for a .mab appended to the exe
// - if nothing is available, will log as hexadecimal pointers, without
// debugging information
// - if aExeName is not specified, will use the current process executable
constructor Create(const aExeName: TFileName=''; MabCreate: boolean=true);
/// save all debugging information in the .mab custom binary format
// - if no file name is specified, it will be saved as ExeName.mab
// - this file content can be appended to the executable via SaveToExe method
// - this function returns the created file name
function SaveToFile(const aFileName: TFileName=''): TFileName;
/// save all debugging informat in our custom binary format
procedure SaveToStream(aStream: TStream);
/// append all debugging information to an executable
// - the executable name must be specified, because it's impossible to
// write to the executable of a running process
procedure SaveToExe(const aExeName: TFileName);
/// add some debugging information according to the specified memory address
// - will create a global TSynMapFile instance for the current process, if
// necessary
// - if no debugging information is available (.map or .mab), will write
// the address as hexadecimal
class procedure Log(W: TTextWriter; Addr: PtrUInt);
/// retrieve a symbol according to an absolute code address
function FindSymbol(aAddr: cardinal): integer;
/// retrieve an unit and source line, according to an absolute code address
function FindUnit(aAddr: cardinal; out LineNumber: integer): integer;
/// return the symbol location according to the supplied absolute address
// - i.e. unit name, symbol name and line number (if any), as plain text
// - returns '' if no match found
function FindLocation(aAddr: Cardinal): RawUTF8;
/// all symbols associated to the executable
property Symbols: TSynMapSymbolDynArray read fSymbol;
/// all units, including line numbers, associated to the executable
property Units: TSynMapUnitDynArray read fUnit;
published
/// the associated file name
property FileName: TFileName read fMapFile;
/// equals true if a .map or .mab debugging information has been loaded
property HasDebugInfo: boolean read fHasDebugInfo;
end;
Thanks to this class, in just one unit, you'll have all the source code line of any location.
Open source, and working with Delphi 5 up to XE (note that the .map format changed a little bit from old to newer versions - our class try to handle it).
Note: This answer addresses the following question.
Is it possible to get source line number at runtime in Delphi?
You can't do this without a map file or something equivalent. The process of compilation leaves the source code behind.
It's not practical to litter your source code with pre-emptive checks for errors. What's more, doing so will only give you very limited information for a very limited number of faults in your code. Generally, if you can anticipate an error, you won't get it wrong. It's the errors that you don't anticipate that make it into production code.
Really you are best off using madExcept, EurekaLog or JclDebug.
var LineNumber: Integer;
procedure MyAssert(const M, F: string; L: Integer; E: Pointer);
begin
LineNumber := L;
end;
procedure TForm1.Button1Click(Sender: TObject);
var I: Integer;
S: TAssertErrorProc;
begin
I := 0;
S := AssertErrorProc;
AssertErrorProc := MyAssert;
try
Assert(I <> 0);
finally
AssertErrorProc := S;
end;
showmessage(IntToStr(LineNumber));
end;
I created my own solution for catching line numbers by assertion.
{$IFDEF TRACELOG}
try
assert(0=1,'');
except on E : Exception do
tracelog(E.Message);
end;
{$ENDIF}
where tracelog() is:
procedure TraceLog(LogMessage: WideString);
var
tfile : TextFile;
logTime : WideString;
begin
logTime := formatDateTime('YYYY-MM-DD HH:NN:SS.zzz',Now);
logMessage := Copy(LogMessage,pos(', line ',LogMessage)+7,pos(')',LogMessage) - pos(', line ',LogMessage) - 7);
Assign(tfile,SrcDir+'data\TraceLog.txt');
if FileExists(SrcDir+'data\TraceLog.txt') then Append(tfile) else Rewrite(tfile);
Writeln(tfile,'{' + logTime + '} [' + LogMessage + ']');
CloseFile(tfile);
end;
and you can Enable/Disable this with Debugger flag
{$DEFINE TRACELOG}
I hope that helps you. I found it a great solution for debuging and tracing linenumbers, let me know if it is helpful.

How operate on TFileStream

Hello recently I replace TextFile with TFileStream. I never use it so I have small problem with it.
How can I add someting to my file after I assign it to variable?
How can I read someting form that file?
I need defined line form that file so I was doing something like that:
var linia_klienta:array[0..30] of string;
AssignFile(tempPlik,'klienci.txt');
Reset(tempPlik);
i:=0;
While Not Eof(tempPlik) do
begin
Readln(tempPlik,linia_klient[i]);
inc(i);
end;
CloseFile(tempPlik);
Then when line two is needed I simply
edit1.text = linia_klienta[1];
If you need to read a text file and access each line, try instead using a TStringList class with this class you can load a file, read the data (accesing each line using a index) and save the data back.
something like this
FText : TStringList;
i : integer;
begin
FText := TStringList.Create;
try
FText.LoadFromFile('C:\Foo\Foo.txt');
//read the lines
for i:=0 to FText.Count-1 do
ProcessLine(FText[i]); //do something
//Add additional lines
FText.Add('Adding a new line to the end');
FText.Add('Adding a new line to the end');
//Save the data back
FText.SaveToFile('C:\Foo\Foo.txt');
finally
FText.Free;
end;
end;
end;
I newer versions of Delphi you can use TStreamReader / TStreamWriter here is an example of using TStreamReader ... this is only for manipulating text files
var
SR : TStreamReader;
line : String;
begin
SR := TStreamReader.Create('D:\test.txt');
while not (SR.EndOfStream) do
begin
line := SR.ReadLine;
ShowMessage(line);
end;
SR.Free;
end;
TStream and its immediate descendants are mostly low-level access class. They mostly deal with generic buffers. There are some more specialized classes that descend from or use a stream to perform higher level tasks.
Since Delphi 1 TReader and TWriter could be used to read and write Delphi types directly (inlcuding strings), but they were not designed to handle "line-oriented" files (unluckily they were designed too much with component properties streaming in mind, not as a general purpose framework).
Turbo Power SysTools has a nice TStAnsiTextStream class that implements line-oriented access to text files in a way similar to that of TextFile. Since Delphi 2009 new classes (see opc0de answer) implement the same kind of access without the need of third party libraries (moreover they support different encodings thanks to Delphi 2009 extend codepage support, including Unicode).
Depending with what you want to do, its the stream class you need.
Do you want to work with text (characters with break-lines and end-of-line characters) data ?
OR, do you want to work with binary data ?
I see you are using an array of char, instead, of a string.
Do you really want to use character data as if it was binary ?
Sometimes, some applications require that case.

Problem adding lots of strings to a TStringList

I have a problem adding strings to a TStringList. I've searched other posts but couldn't find an answer to this.
What I'm trying to do is to add a big amount of strings to a TStringList (more than 14000) but somewhere in the process I get an EAccessViolation. Here's the code I'm using:
procedure TForm1.FormCreate(Sender: TObject);
begin
List := TStringList.Create;
List.Duplicates := dupAccept;
end;
procedure TForm1.ButtonStartClick(Sender: TObject);
begin
List.Clear;
List.Add('125-AMPLE');
List.Add('TCUMSON');
List.Add('ATLV 4300');
List.Add('150T-15');
List.Add('TDL-08ZE');
List.Add('RT20L');
List.Add('SIN LINEA');
List.Add('TIARA');
List.Add('FL200ZK1');
List.Add('FL250ZK1');
List.Add('SIN LINEA');
List.Add('CENTAURO-70 S.P.');
List.Add('CORSADO');
{ This list continues to about 14000 strings...}
List.Add('VOSJOD 2');
List.Add('Z 125');
List.Add('ZUMY');
List.Add('NEW AGE 125');
List.Add('SIN LINEA');
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FreeAndNil(List);
end;
¿What's wrong with this code? The list contains duplicate strings so I set the Duplicates property to dupAccept. I was able to load the list using LoadFromFile, but I don't want to have a text file outside my application.
I hope you can help me!!! Please tell me if you need any further information.
Thank you very much. I really appreciate your help.
The suggestions for using an external file are on the mark here. However, your post indicates your desire for not having an external file. I would then suggest you link the file to the executable as a resource. You can easily do this following these steps:
Place all the strings into a text file called stringdata.txt (or whatever name you choose). Then create a .rc file of whatever name you choose and put the following into it (STRING_DATA can be any identifier you choose):
STRING_DATA RCDATA "stringdata.txt"
Create a .res file from the .rc:
BRCC32 <name of rc>.rc
Now reference this file from the source code. Place the following someplace in the unit:
{$R <name of res>.res}
Instead of loading from a file stream, load from a resource stream:
StringData := TResourceStream.Create(HInstance, 'STRING_DATA', RT_RCDATA);
try
List.LoadFromStream(StringData);
finally
StringData.Free;
end;
If you do command-line automated builds, I would suggest you keep the .rc file under source control and build the .res during the build process. This way you can also keep the stringdata.txt file under source control and any edits are automatically caught on the next build without having to explicitly build the .res file each time the .txt file changes.
What Delphi version are you using? Some older versions had a bug in the memory manager that can cause an access violation when trying to reallocate an array to a size that's too large.
Try adding FastMM4 to your project to replace the old memory manager and see if that helps.
Also, you're probably better off keeping the list in an external file. Yes, it's another file, but it also means that you can change the list without having to recompile the entire program. This also makes creating (and distributing!) updates easier.
Mason is probably right for the cause of the AV; this is quite a large array to grow.
On a side note, when doing such a long processing on a StringList, it's recommended to surround it by BeginUpdate/EndUpdate to avoid firing any update event.
Even if you don't have any now, they might be added later and you'll get problems.
Set list.capacity to the number of items you plan to add, immediately after you create the list. Alternatively, place the list in an RC file (named other than with the name of your project) and add it to your project. This gets compiled into your application, but does not involve executable code to create the list.
I would also worry about compiler integrity with a 14,000 line procedure. People have found other cases where going beyond anything reasonable breaks the compiler in various ways.
You may also want to try THashedStringList, could see a speed boost (although not in this function), although I'm not sure if the add method is a whole lot different.
try using the following instead of your code to add the strings to the StringList
var
Str: string;
begin
Str := '125-AMPLE' + #13#10;
Str := Str + 'TCUMSON' + #13#10;
Str := Str + 'ATLV 4300' + #13#10;
Str := Str + '150T-15' + #13#10;
................
List.Text := Str;
end;

How to delete files matching pattern within a directory

That is, delete all files matching pattern within a given directory
Example, Delete all *.jpg files within DirectoryName
procedure TForm1.Button1Click(Sender: TObject);
begin
DeleteFiles(ExtractFilePath(ParamStr(0)),'*.jpg');
end;
procedure DeleteFiles(APath, AFileSpec: string);
var
lSearchRec:TSearchRec;
lPath:string;
begin
lPath := IncludeTrailingPathDelimiter(APath);
if FindFirst(lPath+AFileSpec,faAnyFile,lSearchRec) = 0 then
begin
try
repeat
SysUtils.DeleteFile(lPath+lSearchRec.Name);
until SysUtils.FindNext(lSearchRec) <> 0;
finally
SysUtils.FindClose(lSearchRec); // Free resources on successful find
end;
end;
end;
In more recent versions of Delphi, you would probably use the classes in System.IOUtils, which are essentially wrapping FindFirst, FindNext etc:
procedure DeleteFilesMatchingPattern(const Directory, Pattern: string);
var FileName: string;
begin
for FileName in TDirectory.GetFiles(Directory, Pattern) do TFile.Delete(FileName);
end;
You can use the SHFileOperation function. The nice thing about using SHFileOperation is you have the option of deleting the files to the recycle bin and you get the normal API animations so the user will know what is going on. The downside is the delete will take a little longer than Jeff's code.
There are several wrappers out there. I use this free wrapper from BP Software. The entire wrapper file is only 220 lines and is easy to read and use. I don't install this as a component. I have found it easier to add this unit to my project and just Create and free the object as needed.
Update: The download link for the BP Software site is no longer valid. There is an older version on the Embarcadero website.
TSHFileOp (1.3.5.1) (3 KB) May
31, 2006 TComponent that is a wrapper
for the SHFileOperation API to copy,
move, rename, or delete (with
recycle-bin support) a file system
object.
The file name parameter for SHFileOperation supports MS DOS style wildcards. So you can use the component like this:
FileOps := TSHFileOp.Create(self);
FileOps.FileList.Add(DirectoryName + '\*.jpg');
FileOps.HWNDHandle := self.Handle;
FileOps.Action := faDelete;
FileOps.SHOptions :=
[ofAllowUndo, ofNoConfirmation, ofFilesOnly, ofSimpleProgress];
FileOps.Execute;
I usually show the "Are you sure" message myself so I always pass the ofNoConfirmation flag so Windows does not ask again.
If you don't want to delete every jpg file or you need to delete from multiple directories you can add full file names or different paths with wild cards to the FileList string list before calling execute.
Here is the MSDN Page for SHFileOperation
Note that SHFileOperation has been replaced by IFileOperation starting with Windows Vista. I have continued to use SHFileOperation on Windows Vista without any problems.

Resources