开发者

How to get line number at runtime

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜