★★★★★超级难题:如何返回函数名称? ( 积分: 200 )

  • 主题发起人 主题发起人 quark
  • 开始时间 开始时间
Q

quark

Unregistered / Unconfirmed
GUEST, unregistred user!
★★★★★超级难题:如何返回函数名称?

例如

function A: string;
begin
怎么能让 Result := 'A';
因为类似的函数很多希望有个解决方案,不能每个函数都手工输入。
end;

200分,不够再加!
 
无法。
这样的设计本身就是错误的。
 
为什么是错误的说来看看
 
程序编译后,就不存在任何常量,变量,过程名字之类的东西,所以无法获取。
要获取不存在的东西,不是设计错误是什么?
 
有个办法,你把这些函数做成输出的,那么编译后在你的PE头里的输出表里就会存在这些函数名.然后函数本身去读自身的输出表来获取函数名.
呵,没具体做过,也许是个思路.
 
哈哈,你的问题真的好变态啊!好久没有见过变态的人问过变态的问题了。我给你一个解决方案:
一定要uses TypInfo。

type
TX = (A);

function EnumToStr(Input: TX):string;
begin
Result := GetEnumName(TypeInfo(TX), Integer(Input) );
end;

然后你调用EnumToStr(A)就返回'A'了。要返回其它的值,只需要修改TX的定义。
 
觀注中。。呵呵。。
 
除非是Class的Published方法.否则函数编译完就是一个代码片段.没有名字的
 
来自:白河愁, 时间:2007-12-11 14:11:42, ID:3858793
程序编译后,就不存在任何常量,变量,过程名字之类的东西,所以无法获取。
要获取不存在的东西,不是设计错误是什么?

好像不是每种类型都如你所说,例如集合类型可以转换为字符串。
单元名称也能够通过特定程序取得。
 
我所指的名字,是 var aa:dword
procedure bb
const aa123='123' 这类的东西
就算是set,实际上也是个 byte 而已,你定义一个叫 aa 或者 bb 的 set 对编译结果无任何影响。 就如 wr960204说的,除了RTII的东西,都是不会使用代码里定义的名字的。
 
类似问题以前已经问过多次了,除了利用RTTI和调试信息没有其他的办法。

下面是一个利用调试信息的解决办法,你可以从返回结果中分离出单元名和函数名。
注意,编译时一定要选中 include TD32 debug info

代码:
uses
  DbgInfo;

function MyName: String;
var
  Offset: DWord;
  di : TDebugInfomation;
begin
  Result := '';
  di := TDebugInfomation.Create(ParamStr(0));
  try
    di.Analyse;
    Offset := DWord(@MyName) - GetModuleHandle(nil) - $1000;
    Result := di.OffsetToProc(Offset).Name;
  finally
    FreeAndNil(di);
  end;
end;
procedure TForm19.Button1Click(Sender: TObject);
begin
  ShowMessage(MyName);
end;


(** 
 * Analyse Symbolic Debugging Information for Borland 32 bit symbol file 
 * 
 * Author     : Flier Lu 
 * EMail      : flier_lu@yahoo.com 
 * Date       : 2001.9.16 
 * Version    : 0.1 
 * Copyrights : The authors grant you the right to modify the source code 
 *              as long as the original authors are mentionned. 
 *              Please let us know if you make any improvements, 
 *              so that I can keep an up to date version. 
 *              We also welcome all comments, preferably by email. 
 * History    : 2001.9.16 - v0.1 - First release for Delphi 6 
 *              2001.9.18 - v0.2 - Analyse dependence between modules, 
 *                                 source files, procedures, etc. 
 *                                 Fix a bug about the NameIndex, all NameIn 
dex 
 *                                 is begin with 1 but Delphi use 0 
 **) 
unit DbgInfo

interface 
uses 
  Windows, Classes, SysUtils, Contnrs

const 
  MaxArraySize = MaxInt div 16 - 1

type 
 PArrayOfByte = ^TArrayOfByte

 TArrayOfByte = array[0..MaxArraySize] of Byte

 PArrayOfWord = ^TArrayOfWord

 TArrayOfWord = array[0..MaxArraySize] of Word

 PArrayOfPointer = ^TArrayOfPointer

 TArrayOfPointer = array[0..MaxArraySize] of PArrayOfByte

{ [-----------------------------------------------------------------------] 
  [         Symbol and Type OMF Format Borland Executable Files          ] 
  [-----------------------------------------------------------------------] 
  Introduction 
  This section describes the format used to embed debugging information  into
  the executable file.
  Debug Information Format
  The format encompasses a block of  data which goes at  the end of the  .EXE
  file,  i.e.,  after   the   header   plus   load   image,   overlays,   and
  Windows/Presentation Manager  resource  compiler  information.   The  lower
  portion of the file is unaffected by the additional data.
  The last eight bytes of the file contain a signature and a long file offset
  from the end of the file (lfoBase).  The signature is FBxx, where xx is the
  version number.  The  long  offset  indicates  the  position  in  the  file
  (relative to the end of the file)  of the base address.  For the LX  format
  executables, the base address  is determined by  looking at the  executable
  header.
  The signatures have the following meanings:
    FB09        The signature for a Borland 32 bit symbol file.
  The value
          lfaBase=length of the file - lfoBase
  gives the base address of the start of the Symbol and Type OMF  information
  relative to  the beginning  of the  file.  All  other file  offsets in  the
  Symbol and Type OMF are relative to  the lfaBase.  At the base address  the
  signature is repeated, followed by the long displacement to the  subsection
  directory (lfoDir).  All subsections start on a long word boundary and  are
  designed to maintain  natural alignment internally  in each subsection  and
  within the subsection directory.
  Subsection Directory
  The subsection directory has the format
       Directory header
       Directory entry 0
       Directory entry 1
        .
        .
        .
       Directory entry n
  There is no requirement for a particular subsection of a particular module
 to exist.
  The following is the layout of the FB09 debug information in the image:
  FB09 Header
    sstModule [1]
    .
    .
    .
    sstModule [n]
    sstAlignSym [1]
    sstSrcModule [1]
    .
    .
    .
    sstAlignSym [n]
    sstSrcModule [n]
    sstGlobalSym
    sstGlobalTypes
    sstNames
    SubSection Directory
  FB09 Trailer
}
const
  Borland32BitSymbolFileSignature = $FB09
// The signature for a Borland 32 bit symbol file.
type
  PFileSignature = ^TFileSignature;
  TFileSignature = packed record
    Signature,      // The signature for a Borland 32 bit symbol file.
    Offset: DWord
 // a long file offset from the end of the file
  end;
// Subsection Types
const
  SUBSECTION_TYPE_MODULE         = $120;
  SUBSECTION_TYPE_TYPES          = $121;
  SUBSECTION_TYPE_SYMBOLS        = $124;
  SUBSECTION_TYPE_ALIGN_SYMBOLS  = $125;
  SUBSECTION_TYPE_SOURCE_MODULE  = $127;
  SUBSECTION_TYPE_GLOBAL_SYMBOLS = $129;
  SUBSECTION_TYPE_GLOBAL_TYPES   = $12B;
  SUBSECTION_TYPE_NAMES          = $130;
type
  { The directory header structure is followed by the directory entries
    which specify the subsection type, module index, file offset, and size.
    The subsection directory gives the location (LFO) and size of each subsection,
    as well as its type and module number if applicable. }
  PDirectoryEntry = ^TDirectoryEntry;
  TDirectoryEntry = packed record
    SubsectionType,    // Subdirectory type
    ModuleIndex: Word
// Module index
    Offset,            // Offset from the base offset lfoBase
    Size: DWord
    // Number of bytes in subsection
  end;
  { The subsection directory is prefixed with a directory header structure
    indicating size and number of subsection directory entries that follow.
}
  PDirectoryHeader = ^TDirectoryHeader;
  TDirectoryHeader = packed record
    Size,               // Length of this structure
    DirEntrySize: Word
// Length of each directory entry
    DirEntryCount,        // Number of directory entries
    lfoNextDir,           // Offset from lfoBase of next directory.
    Flags: DWord
        // Flags describing directory and subsection tables.
    //DirEntries: array[0..0] of TDirectoryEntry;
  end;
{
  SUBSECTION_TYPE_MODULE 0x120
  This describes the basic information about an object module including  code
  segments, module name,  and the  number of  segments for  the modules  that
  follow.  Directory entries for  sstModules  precede  all  other  subsection
  directory entries.
}
type
  PSegmentInfo = ^TSegmentInfo;
  TSegmentInfo = packed record
    Segment,     // Segment that this structure describes
    Flags: Word
// Attributes for the logical segment.
                 // The following attributes are defined:
             //   0x0000 Data segment
             //   0x0001 Code segment
    Offset,      // Offset in segment where the code starts
    Size: DWord
// Count of the number of bytes of code in the segment
  end;
  PModuleInfo = ^TModuleInfo;
  TModuleInfo = packed record
    OverlayNumber,        // Overlay number
    LibraryIndex,         // Index into sstLibraries subsection
                       // if this module was linked from a library
    SegmentCount,         // Count of the number of code segments
                          // this module contributes to
    DebuggingStyle: Word
// Debugging style  for this  module.
    NameIndex,            // Name index of module.
    TimeStamp: DWord
    // Time stamp from the OBJ file.
    Reserved: array[0..2] of DWord
// Set to 0.
    Segments: array[0..MaxArraySize] of TSegmentInfo;
    (*
    { Detailed information about  each segment that  code
   is contributed to.  This is an array of cSeg  count
   segment information descriptor structures. }
    Segments: array[0..SegmentCount - 1] of TSegmentInfo;
    *)
  end;
{ SUBSECTION_TYPE_MODULE 0x0127
  This table describes the source line number to addressing mapping
  information for a module. The table permits the description of a module
  containing multiple source files with each source file contributing code to
  one or more code segments. The base addresses of the tables described
  below are all relative to the beginning of the sstSrcModule table.
  Module header
  Information for source file 1
    Information for segment 1
         .
         .
         .
    Information for segment n
         .
         .
         .
  Information for source file n
    Information for segment 1
         .
         .
         .
    Information for segment n
}
type
  { The line number to address mapping information is contained in a table with
    the following format: }
  PLineMappingEntry = ^TLineMappingEntry;
  TLineMappingEntry = packed record
    SegmentIndex,    // Segment index for this table
    PairCount: Word
// Count of the number of source line pairs to follow
    Offsets: array[0..MaxArraySize] of DWord;
    (*
    { An array of 32-bit offsets for the offset within the code segment of
      the start of ine contained in the parallel array linenumber. }
    Offsets: array[0..PairCount - 1] of DWord;
    { This is an array of 16-bit line numbers of the lines in the source file
      that cause code to be emitted to the code segment.
      This array is parallel to the offset array.
      If cPair is not even, then a zero word is emitted to
      maintain natural alignment in the sstSrcModule table. }
    LineNumbers: array[0..PairCount - 1] of Word;
    *)
  end;
  TOffsetPair = packed record
    StartOffset,
    EndOffset: DWord;
  end;
  { The file table describes the code segments that receive code from this
    source file. Source file entries have the following format: }
  PSourceFileEntry = ^TSourceFileEntry;
  TSourceFileEntry = packed record
    SegmentCount: Word
// Number of segments that receive code from this source file.
    NameIndex: Integer
// Name index of Source file name.
    BaseSrcLines: array[0..MaxArraySize] of DWord;
    (*
    { An array of  offsets for  the line/address  mapping
   tables for each of  the segments that receive  code
   from this source file. }
    BaseSrcLines: array[0..SegmentCount - 1] of DWord;
    { An array  of two  32-bit offsets  per segment  that
   receives code from this  module.  The first  offset
   is the offset within the segment of the first  byte
   of code from this module.  The second offset is the
   ending address of the  code from this module.   The
   order of these pairs corresponds to the ordering of
   the segments in the  seg  array.   Zeros  in  these
   entries means that the information is not known and
   the file and line tables described below need to be
   examined to determine if an address of interest  is
   contained within the code from this module. }
    SegmentAddress: array[0..SegmentCount - 1] of TOffsetPair;
    Name: ShortString
// Count of the number of bytes in source file name
    *)
  end;
  { The module header structure describes the source file and code segment
    organization of the module. Each module header has the following format:
 }
  PSourceModuleInfo = ^TSourceModuleInfo;
  TSourceModuleInfo = packed record
    FileCount,          // The number of source file scontributing code to segments
    SegmentCount: Word
// The number of code segments receiving code from this module
    BaseSrcFiles: array[0..MaxArraySize] of DWord;
    (*
    // This is an array of base offsets from the beginning of the sstSrcModule table
    BaseSrcFiles: array[0..FileCount - 1] of DWord;
    { An array  of two  32-bit offsets  per segment  that
   receives code from this  module.  The first  offset
   is the offset within the segment of the first  byte
   of code from this module.  The second offset is the
   ending address of the  code from this module.   The
   order of these pairs corresponds to the ordering of
   the segments in the  seg  array.   Zeros  in  these
   entries means that the information is not known and
   the file and line tables described below need to be
   examined to determine if an address of interest  is
   contained within the code from this module. }
    SegmentAddress: array[0..SegmentCount - 1] of TOffsetPair;
    { An array of segment indices that receive code  from
   this module.  If the  number  of  segments  is  not
   even, a pad word  is inserted  to maintain  natural
   alignment. }
    SegmentIndexes: array[0..SegmentCount - 1] of Word;
    *)
  end;
{ SUBSECTION_TYPE_GLOBAL_TYPES 0x12b
  This subsection contains the packed  type records for the executable  file.
  The first long word of the subsection  contains the number of types in  the
  table.  This count is  followed by a count-sized  array of long offsets  to
  the  corresponding  type  record.   As  the  sstGlobalTypes  subsection  is
  written, each  type record  is forced  to start  on a  long word  boundary.
  However, the length of the  type string is NOT  adjusted by the pad  count.
  The remainder of the subsection contains  the type records.
}
type
  PGlobalTypeInfo = ^TGlobalTypeInfo;
  TGlobalTypeInfo = packed record
    Count: DWord
// count of the number of types
    // offset of each type string from the beginning of table
    Offsets: array[0..MaxArraySize] of DWord;
  end;
{ Symbol type defines }
const
  SYMBOL_TYPE_COMPILE        = $0001
// Compile flags symbol
  SYMBOL_TYPE_REGISTER       = $0002
// Register variable
  SYMBOL_TYPE_CONST          = $0003
// Constant symbol
  SYMBOL_TYPE_UDT            = $0004
// User-defined Type
  SYMBOL_TYPE_SSEARCH        = $0005
// Start search
  SYMBOL_TYPE_END            = $0006
// End block, procedure, with, or thunk
  SYMBOL_TYPE_SKIP           = $0007
// Skip - Reserve symbol space
  SYMBOL_TYPE_CVRESERVE      = $0008
// Reserved for Code View internal use

  SYMBOL_TYPE_OBJNAME        = $0009
// Specify name of object file
  SYMBOL_TYPE_BPREL16        = $0100
// BP relative 16:16
  SYMBOL_TYPE_LDATA16        = $0101
// Local data 16:16
  SYMBOL_TYPE_GDATA16        = $0102
// Global data 16:16
  SYMBOL_TYPE_PUB16          = $0103
// Public symbol 16:16
  SYMBOL_TYPE_LPROC16        = $0104
// Local procedure start 16:16
  SYMBOL_TYPE_GPROC16        = $0105
// Global procedure start 16:16
  SYMBOL_TYPE_THUNK16        = $0106
// Thunk start 16:16
  SYMBOL_TYPE_BLOCK16        = $0107
// Block start 16:16
  SYMBOL_TYPE_WITH16         = $0108
// With start 16:16
  SYMBOL_TYPE_LABEL16        = $0109
// Code label 16:16
  SYMBOL_TYPE_CEXMODEL16     = $010A
// Change execution model 16:16
  SYMBOL_TYPE_VFTPATH16      = $010B
// Virtual function table path descriptor 16:16
  SYMBOL_TYPE_BPREL32        = $0200
// BP relative 16:32
  SYMBOL_TYPE_LDATA32        = $0201
// Local data 16:32
  SYMBOL_TYPE_GDATA32        = $0202
// Global data 16:32
  SYMBOL_TYPE_PUB32          = $0203
// Public symbol 16:32
  SYMBOL_TYPE_LPROC32        = $0204
// Local procedure start 16:32
  SYMBOL_TYPE_GPROC32        = $0205
// Global procedure start 16:32
  SYMBOL_TYPE_THUNK32        = $0206
// Thunk start 16:32
  SYMBOL_TYPE_BLOCK32        = $0207
// Block start 16:32
  SYMBOL_TYPE_WITH32         = $0208
// With start 16:32
  SYMBOL_TYPE_LABEL32        = $0209
// Label 16:32
  SYMBOL_TYPE_CEXMODEL32     = $020A
// Change execution model 16:32
  SYMBOL_TYPE_VFTPATH32      = $020B
// Virtual function table path descriptor 16:32
{
  Compile Flag
  SYMBOL_TYPE_COMPILE 0x0001
    This symbol communicates to Code View compile time information on
  a per module basis, such as the language and version number of the compiler,
  the ambient model for code and data, and the target processor.
  Flags - flags showing compile time options:
    PCodePresent            :1
    FloatPrecision          :2
    FloatPackage            :2
    AmbientData             :3
    AmbientCode             :3
    Mode32                  :1     Compiled for 32 bit addresses
    chsign               :1     True is 'char' is a signed type
    Reserved                :3
                Ambient code and data memory model enumeration
                0       Near
                1       Far
                2       Huge
                3-7     Reserved
                Floating package enumeration
                0       Hardware processor (80x87 for Intel 80x86 processors)
                1       Emulator
                2       Altmath
                3       Reserved
                The FloatPrecision flag is set  to 1 if the  -OP option was enabled  during
                the compilation.
}
const
  COMPILE_MACHINE_INTEL_8080  = $00;
  COMPILE_MACHINE_INTEL_8086  = $01;
  COMPILE_MACHINE_INTEL_80286 = $02;
  COMPILE_MACHINE_INTEL_80386 = $03;
  COMPILE_MACHINE_INTEL_80486 = $04;
  COMPILE_MACHINE_MIPS_R4000  = $10;
  COMPILE_LANGUAGE_C       = 0;
  COMPILE_LANGUAGE_CPP     = 1;
  COMPILE_LANGUAGE_FORTRAN = 2;
  COMPILE_LANGUAGE_MASM    = 3;
  COMPILE_LANGUAGE_PASCAL  = 4;
  COMPILE_LANGUAGE_BASIC   = 5;
  COMPILE_LANGUAGE_COBOL   = 6;
  COMPILE_FLOATING_PACKAGE_HARDWARE = 0;
  COMPILE_FLOATING_PACKAGE_EMULATOR = 1;
  COMPILE_FLOATING_PACKAGE_ALTMATH  = 2;
  COMPILE_MEMORY_MODEL_NEAR = 0;
  COMPILE_MEMORY_MODEL_FAR  = 1;
  COMPILE_MEMORY_MODEL_HUGE = 2;
type
  TSymbolCompileInfo = packed record
    Machine,
    Language: Byte;
    Flags: Word;
    Version: ShortString;
  end;
{
  Global and Local Procedure Start 16:32
  SYMBOL_TYPE_LPROC32 0x0204
  S_GPROC32 0x0205
    The symbol records define local (file static) and global procedure
  definition. For C/C++, functions that are declared static to a module are
  emitted as Local Procedure symbols. Functions not specifically declared
  static are emitted as Global Procedures. For each S_GPROC32 emitted,
  an S_GPROCREF symbol must be fabricated and emitted to the sstGlobalSym section.
}
type
  TSymbolProcInfo = packed record
    pParent,
    pEnd,
    pNext,
    Size,             // Length in bytes of this procedure
    DebugStart,       // Offset in bytes from the start of the procedure to
                   // the point where the stack frame has been set up.
    DebugEnd,         // Offset in bytes from the start of the procedure to
                   // the point where the  procedure is  ready to  return
                   // and has calculated its return value, if any.
                      // Frame and register variables an still be viewed.
    Offset:DWord
    // Offset portion of  the segmented address of
                      // the start of the procedure in the code segment
    Segment: Word
   // Segment portion of the segmented address of
                      // the start of the procedure in the code segment
    ProcType: DWord
 // Type of the procedure type record
    NearFar,          // Type of return the procedure makes:
                      //   0       near
                      //   4       far
    Reserved: Byte;
    NameIndex: DWord
// Name index of procedure
  end;
type
  PSymbolInfo = ^TSymbolInfo;
  TSymbolInfo = packed record
    Size,
    SymbolType: Word;
    case Word of
      SYMBOL_TYPE_COMPILE:   ( Compile: TSymbolCompileInfo
);
      SYMBOL_TYPE_LPROC32,
      SYMBOL_TYPE_GPROC32:   ( Proc: TSymbolProcInfo
);
  end;
  PSymbolInfos = ^TSymbolInfos;
  TSymbolInfos = packed record
    Signature: DWord;
    Symbols: array[0..0] of TSymbolInfo;
  end;
type
  TSegmentType = (stData, stCode);
  TSegment = class
  private
    FSegmentType: TSegmentType;
    FOffset,
    FSize: DWord;
  public
    constructor Create(const ASegmentType: TSegmentType
const AOffset, ASize: DWord);
    property SegmentType: TSegmentType read FSegmentType;
    property Offset: DWord read FOffset;
    property Size: DWord read FSize;
  end;
  TDebugInfomation = class;
  TSourceFile = class;
  TSymbolProc = class;
  TModule = class
  private
    FOwner: TDebugInfomation;
    FNameIndex: Integer;
    FSegments,
    FProcs: TObjectList;
    function GetName: string;
    function GetSegment(const Index: Integer): TSegment;
    function GetSegmentCount: Integer;
    function GetProc(const Index: Integer): TSymbolProc;
    function GetProcCount: Integer;
  protected
    FSourceFile: TSourceFile;
    function AddSegment(const ASegment: TSegment): Integer;
    function AddProc(const AProc: TSymbolProc): Integer;
    property NameIndex: Integer read FNameIndex;
  public
    constructor Create(const AOwner: TDebugInfomation
const ANameIndex: Integer);
    destructor Destroy
override;
    function IsContainOffset(const AOffset: DWord): Boolean;
    function OffsetToProc(const AOffset: DWord): TSymbolProc;
    property Owner: TDebugInfomation read FOwner;
    property SourceFile: TSourceFile read FSourceFile;
    property Name: string read GetName;
    property Segment[const Index: Integer]: TSegment read GetSegment;
    property SegmentCount: Integer read GetSegmentCount;
    property Proc[const Index: Integer]: TSymbolProc read GetProc;
    property ProcCount: Integer read GetProcCount;
  end;
  TLineAddress = class
  private
    FOffset: DWord;
    FLineNo: Integer;
  public
    constructor Create(const AOffset: DWord
const ALineNo: Integer);
    property Offset: DWord read FOffset;
    property LineNo: Integer read FLineNo;
  end;
  TSourceFile = class
  private
    FOwner: TDebugInfomation;
    FNameIndex: Integer;
    FAddresses: TObjectList;
    function GetName: string;
    function GetAddress(const Index: Integer): TLineAddress;
    function GetAddressCount: Integer;
  protected
    FModule: TModule;
    function AddAddress(const AAddress: TLineAddress): Integer;
    property NameIndex: Integer read FNameIndex;
  public
    constructor Create(const AOwner: TDebugInfomation
const ANameIndex: Integer);
    destructor Destroy
override;
    function OffsetToLineAddress(const AOffset: DWord): TLineAddress;
    function OffsetToLineNo(const AOffset: DWord): Integer;
    function LineNoToOffset(const ALineNo: Integer): DWord;
    property Owner: TDebugInfomation read FOwner;
    property Module: TModule read FModule;
    property Name: string read GetName;
    property Address[const Index: Integer]: TLineAddress read GetAddress;
    property AddressCount: Integer read GetAddressCount;
  end;
  TSymbol = class
  private
    FOwner: TDebugInfomation;
    FSymbolType: Word;
  protected
    procedure Analyse(const pInfo: PSymbolInfo)
virtual
abstract;
  public
    constructor Create(const AOwner: TDebugInfomation
const pInfo: PSymbolInfo);
    property SymbolType: Word read FSymbolType;
  end;
  TCompileMachine = (cm8080, cm8086, cm80286, cm80386, cm80486, cmR4K, cmUnknown);
  TCompileLanguage = (clC, clCpp, clFortran, clMasm, clPascal, clBasic, clCobol, clUnknown);
  TCompileFloatPackage = (fpHardware, fpEmulator, fpAltmath, fpUnknown);
  TCompileMemoryModel = (mmNear, mmFar, mmHuge, mmUnknown);
  TSymbolCompile = class(TSymbol)
  private
    FMachine: TCompileMachine;
    FLanguage: TCompileLanguage;
    FPCodePresent: Boolean;
    FFloatPrecision: Byte;
    FFloatPackage: TCompileFloatPackage;
    FAmbientData: TCompileMemoryModel;
    FAmbientCode: TCompileMemoryModel;
    FMode32: Boolean;
    FCharSign: Boolean;
    FVersion: string;
  protected
    procedure Analyse(const pInfo: PSymbolInfo)
override;
  public
    property Machine: TCompileMachine read FMachine;
    property Language: TCompileLanguage read FLanguage;
    property PCodePresent: Boolean read FPCodePresent;
    property FloatPrecision: Byte read FFloatPrecision;
    property FloatPackage: TCompileFloatPackage read FFloatPackage;
    property AmbientData: TCompileMemoryModel read FAmbientData;
    property AmbientCode: TCompileMemoryModel read FAmbientCode;
    property Mode32: Boolean read FMode32;
    property CharSign: Boolean read FCharSign;
    property Version: string read FVersion;
  end;
  TSymbolProc = class(TSymbol)
  private
    FNameIndex: Integer;
    FOffset,
    FSize: DWord;
    function GetName: string;
  protected
    FModule: TModule;
    procedure Analyse(const pInfo: PSymbolInfo)
override;
    property NameIndex: Integer read FNameIndex;
  public
    function IsContainOffset(const AOffset: DWord): Boolean;
    property Module: TModule read FModule;
    property Name: string read GetName;
    property Offset: DWord read FOffset;
    property Size: DWord read FSize;
  end;
  TSymbolLocalProc = class(TSymbolProc);
  TSymbolGlobalProc = class(TSymbolProc);
  TDebugInfomation = class
  private
    FFileName: TFileName;
    FFileSize: DWord;
    m_hFile,
    m_hFileMapping: THandle;
    m_pBaseAddr: Pointer;
    m_lfaBase: DWord
// the base address of the start of
                      // the Symbol and Type OMF information
                      // relative to the beginning of the file
    FTypes,
    FNames: TStringList;
    FSymbols,
    FModules,
    FSourceFiles: TObjectList;
    function IsBorland32BitSymbolFileSignature(const Signature: DWord): Boolean;
    function GetSymbol(const Index: Integer): TSymbol;
    function GetSymbolCount: Integer;
    function GetModule(const Index: Integer): TModule;
    function GetModuleCount: Integer;
    function GetSourceFile(const Index: Integer): TSourceFile;
    function GetSourceFileCount: Integer;
  protected
    procedure AnalyseModules(const pModInfo: PModuleInfo);
    procedure AnalyseTypes(const pSubsection: Pointer);
    procedure AnalyseSymbols(const pSubsection: Pointer);
    procedure AnalyseAlignSymbols(const pSymbols: PSymbolInfos
const Size: DWord);
    procedure AnalyseSourceModules(const pSrcModInfo: PSourceModuleInfo);
    procedure AnalyseGlobalSymbols(const pSubsection: Pointer);
    procedure AnalyseGlobalTypes(const pTypes: PGlobalTypeInfo);
    procedure AnalyseNames(const pSubsection: Pointer);
    procedure AnalyseDependence;
  public
    constructor Create(const AFileName: TFileName);
    destructor Destroy
override;
    procedure Analyse;
    function OffsetToModule(const AOffset: DWord): TModule;
    function OffsetToProc(const AOffset: DWord): TSymbolProc;
    function OffsetToLineAddress(const AOffset: DWord): TLineAddress;
    function OffsetToLineNo(const AOffset: DWord): Integer;
    property FileName: TFileName read FFileName;
    property FileSize: DWord read FFileSize;
    property Types: TStringList read FTypes;
    property Names: TStringList read FNames;
    property Symbol[const Index: Integer]: TSymbol read GetSymbol;
    property SymbolCount: Integer read GetSymbolCount;
    property Module[const Index: Integer]: TModule read GetModule;
    property ModuleCount: Integer read GetModuleCount;
    property SourceFile[const Index: Integer]: TSourceFile read GetSourceFile;
    property SourceFileCount: Integer read GetSourceFileCount;
  end;
implementation
{$WARN SYMBOL_DEPRECATED OFF}
{$WARN SYMBOL_PLATFORM OFF}
resourcestring
  SFileNotExist        = 'The file %s is not exist!';
  SFileHasNotDbgInfo   = 'The file %s has not debug infomation!';
{ TSegment }
constructor TSegment.Create(const ASegmentType: TSegmentType;
  const AOffset, ASize: DWord);
begin
  inherited Create;
  FSegmentType := ASegmentType;
  FOffset      := AOffset;
  FSize        := ASize;
end;
{ TModule }
constructor TModule.Create(const AOwner: TDebugInfomation
const ANameIndex: Integer);
begin
  Assert(Assigned(AOwner));
  Assert(ANameIndex > 0);
  inherited Create;
  FOwner      := AOwner;
  FNameIndex  := ANameIndex;
  FSegments   := TObjectList.Create;
  FProcs      := TObjectList.Create(False);
  FSourceFile := nil;
end;
destructor TModule.Destroy;
begin
  FProcs.Free;
  FSegments.Free;
  inherited;
end;
function TModule.GetName: string;
begin
  Result := FOwner.Names[FNameIndex - 1];
end;
function TModule.GetSegment(const Index: Integer): TSegment;
begin
  Assert((Index >= 0) and (Index < FSegments.Count));
  Result := FSegments.Items[Index] as TSegment;
end;
function TModule.GetSegmentCount: Integer;
begin
  Result := FSegments.Count;
end;
function TModule.AddSegment(const ASegment: TSegment): Integer;
begin
  Assert(Assigned(ASegment));
  Result := FSegments.Add(ASegment);
end;
function TModule.GetProc(const Index: Integer): TSymbolProc;
begin
  Assert((Index >= 0) and (Index < FProcs.Count));
  Result := FProcs.Items[Index] as TSymbolProc;
end;
function TModule.GetProcCount: Integer;
begin
  Result := FProcs.Count;
end;
function TModule.AddProc(const AProc: TSymbolProc): Integer;
begin
  Assert(Assigned(AProc));
  Result := FProcs.Add(AProc);
end;
function TModule.IsContainOffset(const AOffset: DWord): Boolean;
var
  I: Integer;
begin
  for I := 0 to SegmentCount - 1 do
    if Segment[I].SegmentType = stCode then
      if (Segment[I].Offset <= AOffset) and
        (AOffset < (Segment[I].Offset + Segment[I].Size)) then
      begin
        Result := True;
        Exit;
      end;
  Result := False;
end;
function TModule.OffsetToProc(const AOffset: DWord): TSymbolProc;
var
  I: Integer;
begin
  for I := 0 to ProcCount - 1 do
    if Proc[I].IsContainOffset(AOffset) then
    begin
      Result := Proc[I];
      Exit;
    end;
  Result := nil;
end;
{ TLineAddress }
constructor TLineAddress.Create(const AOffset: DWord
const ALineNo: Integer);
begin
  inherited Create;
  FOffset := AOffset;
  FLineNo  := ALineNo;
end;
{ TSourceFile }
constructor TSourceFile.Create(const AOwner: TDebugInfomation
const ANameIndex: Integer);
begin
  Assert(Assigned(AOwner));
  Assert(ANameIndex > 0);
  inherited Create;
  FOwner     := AOwner;
  FNameIndex := ANameIndex;
  FAddresses := TObjectList.Create;
  FModule    := nil;
end;
destructor TSourceFile.Destroy;
begin
  FAddresses.Free;
  inherited;
end;
function TSourceFile.GetName: string;
begin
  Result := FOwner.Names[FNameIndex - 1];
end;
function TSourceFile.GetAddress(const Index: Integer): TLineAddress;
begin
  Assert((Index >= 0) and (Index < FAddresses.Count));
  Result := FAddresses.Items[Index] as TLineAddress;
end;
function TSourceFile.GetAddressCount: Integer;
begin
  Result := FAddresses.Count;
end;
function TSourceFile.AddAddress(const AAddress: TLineAddress): Integer;
begin
  Assert(Assigned(AAddress));
  Result := FAddresses.Add(AAddress);
end;
function TSourceFile.OffsetToLineAddress(const AOffset: DWord): TLineAddress;
var
  I: Integer;
begin
  Assert(AddressCount > 0);
  Result := nil;
  if AOffset < Address[0].Offset then
    Exit;
  for I := 1 to AddressCount - 1 do
    if (Address[I - 1].Offset <= AOffset) and (AOffset < Address[I].Offset)
then
    begin
      Result := Address[I - 1];
      Break;
    end;
end;
function TSourceFile.OffsetToLineNo(const AOffset: DWord): Integer;
var
  ALineAddr: TLineAddress;
begin
  ALineAddr := OffsetToLineAddress(AOffset);
  if Assigned(ALineAddr) then
    Result := ALineAddr.LineNo
  else
    Result := -1;
end;
function TSourceFile.LineNoToOffset(const ALineNo: Integer): DWord;
var
  I: Integer;
begin
  for I := 0 to AddressCount - 1 do
    if Address[I].LineNo = ALineNo then
    begin
      Result := Address[I].Offset;
      Exit;
    end;
  Result := DWord(-1);
end;
{ TSymbol }
constructor TSymbol.Create(const AOwner: TDebugInfomation
const pInfo: PSymbolInfo);
begin
  inherited Create;
  FOwner := AOwner;
  Analyse(pInfo);
end;
{ TSymbolCompile }
procedure TSymbolCompile.Analyse(const pInfo: PSymbolInfo);
begin
  Assert(pInfo.SymbolType = SYMBOL_TYPE_COMPILE);
  case pInfo.Compile.Machine of
    COMPILE_MACHINE_INTEL_8080:  FMachine := cm8080;
    COMPILE_MACHINE_INTEL_8086:  FMachine := cm8086;
    COMPILE_MACHINE_INTEL_80286: FMachine := cm80286;
    COMPILE_MACHINE_INTEL_80386: FMachine := cm80386;
    COMPILE_MACHINE_INTEL_80486: FMachine := cm80486;
    COMPILE_MACHINE_MIPS_R4000:  FMachine := cmR4K;
  else
    FMachine := cmUnknown;
  end;
  case pInfo.Compile.Language of
    COMPILE_LANGUAGE_C:       FLanguage := clC;
    COMPILE_LANGUAGE_CPP:     FLanguage := clCpp;
    COMPILE_LANGUAGE_FORTRAN: FLanguage := clFortran;
    COMPILE_LANGUAGE_MASM:    FLanguage := clMasm;
    COMPILE_LANGUAGE_PASCAL:  FLanguage := clPascal;
    COMPILE_LANGUAGE_BASIC:   FLanguage := clBasic;
    COMPILE_LANGUAGE_COBOL:   FLanguage := clCobol;
  else
    FLanguage := clUnknown;
  end;
  FPCodePresent   := (pInfo.Compile.Flags and $0001) <> 0
 // 1 bit from 0 bit
  FFloatPrecision := (pInfo.Compile.Flags and $0006) shr 1
// 2 bit from 1 bit
  case (pInfo.Compile.Flags and $0018) shr 3 of // 2 bit from 3 bit
    COMPILE_FLOATING_PACKAGE_HARDWARE: FFloatPackage := fpHardware;
    COMPILE_FLOATING_PACKAGE_EMULATOR: FFloatPackage := fpEmulator;
    COMPILE_FLOATING_PACKAGE_ALTMATH:  FFloatPackage := fpAltmath;
  else
    FFloatPackage := fpUnknown;
  end;
  case (pInfo.Compile.Flags and $00E0) shr 5 of // 3 bit from 5 bit
    COMPILE_MEMORY_MODEL_NEAR: FAmbientData := mmNear;
    COMPILE_MEMORY_MODEL_FAR:  FAmbientData := mmFar;
    COMPILE_MEMORY_MODEL_HUGE: FAmbientData := mmHuge;
  else
    FAmbientData := mmUnknown;
  end;
  case (pInfo.Compile.Flags and $0700) shr 8 of // 3 bit from 8 bit
    COMPILE_MEMORY_MODEL_NEAR: FAmbientCode := mmNear;
    COMPILE_MEMORY_MODEL_FAR:  FAmbientCode := mmFar;
    COMPILE_MEMORY_MODEL_HUGE: FAmbientCode := mmHuge;
  else
    FAmbientCode := mmUnknown;
  end;
  FMode32   := (pInfo.Compile.Flags and $0800) <> 0
 // 1 bit from 11 bit
  FCharSign := (pInfo.Compile.Flags and $1000) <> 0
 // 1 bit from 12 bit
  FVersion := pInfo.Compile.Version;
end;
{ TSymbolProc }
function TSymbolProc.GetName: string;
begin
  Result := FOwner.Names[FNameIndex - 1];
end;
procedure TSymbolProc.Analyse(const pInfo: PSymbolInfo);
begin
  FNameIndex := pInfo.Proc.NameIndex;
  FOffset    := pInfo.Proc.Offset;
  FSize      := pInfo.Proc.Size;
  FModule    := nil;
end;
function TSymbolProc.IsContainOffset(const AOffset: DWord): Boolean;
begin
  Result := (FOffset <= AOffset) and (AOffset < (FOffset + FSize));
end;
{ TDebugInfomation }
constructor TDebugInfomation.Create(const AFileName: TFileName);
var
  pFileSign: PFileSignature;
begin
  inherited Create;
  FFileName := ExpandFileName(AFileName);
  // Check the file is exist ?
  if not FileExists(FFileName) then
    raise Exception.CreateFmt(SFileNotExist, [FFileName]);
  // Open the file for read
  m_hFile := CreateFile(PChar(FFileName), GENERIC_READ, FILE_SHARE_READ,
    nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if m_hFile = INVALID_HANDLE_VALUE then
    RaiseLastWin32Error;
  // Get the file size
  FFileSize := GetFileSize(m_hFile, nil);
  if FFileSize = INVALID_FILE_SIZE then
    RaiseLastWin32Error;
  // Create the file mapping handle for read only
  m_hFileMapping := CreateFileMapping(m_hFile, nil, PAGE_READONLY,
    0, FFileSize, nil);
  if m_hFileMapping = 0 then
    RaiseLastWin32Error;
  // Map the file to memory for read
  m_pBaseAddr := MapViewOfFile(m_hFileMapping, FILE_MAP_READ, 0, 0, 0);
  if m_pBaseAddr = nil then
    RaiseLastWin32Error;
  // Check the debug information is exist ?
  pFileSign := PFileSignature(DWord(m_pBaseAddr) + FFileSize - SizeOf(TFileSignature));
  if IsBorland32BitSymbolFileSignature(pFileSign.Signature) then
    raise Exception.CreateFmt(SFileHasNotDbgInfo, [FFileName]);
  // Calculate the base address of the start of the Symbol and Type OMF information
  m_lfaBase := DWord(m_pBaseAddr) + FFileSize - pFileSign.Offset;
  // Check the debug information is exist ?
  pFileSign := PFileSignature(m_lfaBase);
  if IsBorland32BitSymbolFileSignature(pFileSign.Signature) then
    raise Exception.CreateFmt(SFileHasNotDbgInfo, [FFileName]);
  FTypes       := TStringList.Create;
  FNames       := TStringList.Create;
  FSymbols     := TObjectList.Create;
  FModules     := TObjectList.Create;
  FSourceFiles := TObjectList.Create;
end;
destructor TDebugInfomation.Destroy;
begin
  FSourceFiles.Free;
  FModules.Free;
  FSymbols.Free;
  FNames.Free;
  FTypes.Free;
  Assert(m_hFile <> INVALID_HANDLE_VALUE);
  Assert(m_hFileMapping <> 0);
  Assert(m_pBaseAddr <> nil);
  Win32Check(UnmapViewOfFile(m_pBaseAddr));
  Win32Check(CloseHandle(m_hFileMapping));
  Win32Check(CloseHandle(m_hFile));
  inherited;
end;
function TDebugInfomation.IsBorland32BitSymbolFileSignature(const Signature:
 DWord): Boolean;
begin
  Result := Signature = Borland32BitSymbolFileSignature;
end;
function TDebugInfomation.GetSymbol(const Index: Integer): TSymbol;
begin
  Assert((Index >= 0) and (Index < FSymbols.Count));
  Result := FSymbols.Items[Index] as TSymbol;
end;
function TDebugInfomation.GetSymbolCount: Integer;
begin
  Result := FSymbols.Count;
end;
function TDebugInfomation.GetModule(const Index: Integer): TModule;
begin
  Assert((Index >= 0) and (Index < FModules.Count));
  Result := FModules.Items[Index] as TModule;
end;
function TDebugInfomation.GetModuleCount: Integer;
begin
  Result := FModules.Count;
end;
function TDebugInfomation.GetSourceFile(const Index: Integer): TSourceFile;
begin
  Assert((Index >= 0) and (Index < FSourceFiles.Count));
  Result := FSourceFiles.Items[Index] as TSourceFile;
end;
function TDebugInfomation.GetSourceFileCount: Integer;
begin
  Result := FSourceFiles.Count;
end;
procedure TDebugInfomation.AnalyseModules(const pModInfo: PModuleInfo);
const
  SegmentTypes: array[0..1] of TSegmentType = (stData, stCode);
var
  I: Integer;
  Module: TModule;
begin
  Module := TModule.Create(Self, pModInfo.NameIndex);
  FModules.Add(Module);
  for I := 0 to pModInfo.SegmentCount - 1 do
    with pModInfo.Segments[I] do
      Module.AddSegment(TSegment.Create(SegmentTypes[Flags], Offset, Size));

end;
procedure TDebugInfomation.AnalyseTypes(const pSubsection: Pointer);
begin
end;
procedure TDebugInfomation.AnalyseSymbols(const pSubsection: Pointer);
begin
end;
procedure TDebugInfomation.AnalyseAlignSymbols(const pSymbols: PSymbolInfos;
 const Size: DWord);
var
  Offset: DWord;
  pInfo: PSymbolInfo;
  Symbol: TSymbol;
begin
  Offset := DWord(@pSymbols.Symbols[0]) - DWord(pSymbols);
  while Offset < Size do
  begin
    pInfo := PSymbolInfo(DWord(pSymbols) + Offset);
    case pInfo.SymbolType of
      SYMBOL_TYPE_COMPILE: Symbol := TSymbolCompile.Create(Self, pInfo);
      SYMBOL_TYPE_LPROC32: Symbol := TSymbolLocalProc.Create(Self, pInfo);
      SYMBOL_TYPE_GPROC32: Symbol := TSymbolGlobalProc.Create(Self, pInfo);
    else
      Symbol := nil;
    end;
    if Assigned(Symbol) then
      FSymbols.Add(Symbol);
    Inc(Offset, pInfo.Size + SizeOf(pInfo.Size));
  end;
end;
procedure TDebugInfomation.AnalyseSourceModules(const pSrcModInfo: PSourceModuleInfo);
var
  I, J, K, Offset, LineNo: Integer;
  pSrcFile: PSourceFileEntry;
  SourceFile: TSourceFile;
  pLineEntry: PLineMappingEntry;
begin
  for I := 0 to pSrcModInfo.FileCount - 1 do
  begin
    pSrcFile := PSourceFileEntry(DWord(pSrcModInfo) + pSrcModInfo.BaseSrcFiles[I]);
    if pSrcFile.NameIndex > 0 then
    begin
      SourceFile := TSourceFile.Create(Self, pSrcFile.NameIndex);
      FSourceFiles.Add(SourceFile);
      for J := 0 to pSrcFile.SegmentCount - 1 do
      begin
        pLineEntry := PLineMappingEntry(DWord(pSrcModInfo) + pSrcFile.BaseSrcLines[J]);
        for K := 0 to pLineEntry.PairCount - 1 do
        begin
          Offset := pLineEntry.Offsets[K];
          LineNo := PArrayOfWord(@pLineEntry.Offsets[pLineEntry.PairCount])^[K];
          SourceFile.AddAddress(TLineAddress.Create(Offset, LineNo));
        end;
      end;
    end;
  end;
end;
procedure TDebugInfomation.AnalyseGlobalSymbols(const pSubsection: Pointer);
begin
end;
procedure TDebugInfomation.AnalyseGlobalTypes(const pTypes: PGlobalTypeInfo);
begin
end;
procedure TDebugInfomation.AnalyseNames(const pSubsection: Pointer);
var
  I, Count, Len: Integer;
  pszName: PChar;
begin
  Count := PDWord(pSubsection)^;
  pszName := PChar(DWord(pSubsection) + SizeOf(DWord));
  FNames.BeginUpdate;
  try
    for I := 0 to Count - 1 do
    begin
      // Get the length of the name
      Len := Ord(pszName^);
      Inc(pszName);
      // Get the name
      FNames.Add(pszName);
      // skip the length of name and a NULL at the end
      Inc(pszName, Len + 1);
    end;
  finally
    FNames.EndUpdate;
  end;
end;
procedure TDebugInfomation.AnalyseDependence;
var
  I, J: Integer;
  AMod: TModule;
  ASrc: TSourceFile;
  AProc: TSymbolProc;
begin
  // Analyse the dependence between modules and source files
  for I := 0 to ModuleCount - 1 do
  begin
    AMod := Module[I];
    if Assigned(AMod.SourceFile) then
      Continue;
    for J := 0 to SourceFileCount - 1 do
    begin
      ASrc := SourceFile[J];
      if Assigned(ASrc.Module) then
        Continue;
      Assert(ASrc.AddressCount > 0);
      if AMod.IsContainOffset(ASrc.Address[0].Offset) then
      begin
        AMod.FSourceFile := ASrc;
        ASrc.FModule     := AMod;
        Break;
      end;
    end;
  end;
  // Analyse the dependence between modules and procedures
  for I := 0 to SymbolCount - 1 do
  begin
    if Symbol[I].InheritsFrom(TSymbolProc) then
    begin
      AProc := Symbol[I] as TSymbolProc;
      for J := 0 to ModuleCount - 1 do
      begin
        AMod := Module[J];
        if AMod.IsContainOffset(AProc.Offset) then
        begin
          AMod.FProcs.Add(AProc);
          AProc.FModule := AMod;
          Break;
        end;
      end;
    end;
  end;
end;
procedure TDebugInfomation.Analyse;
var
  I: Integer;
  pDirHeader: PDirectoryHeader;
  pDirEntry: PDirectoryEntry;
  pSubsection: Pointer;
begin
  pDirHeader := PDirectoryHeader(m_lfaBase + PFileSignature(m_lfaBase).Offset);
  for I := 0 to pDirHeader.DirEntryCount - 1 do
  begin
    pDirEntry := PDirectoryEntry(DWord(pDirHeader) + pDirHeader.Size +
                                 DWord(pDirHeader.DirEntrySize * I));
    pSubsection := Pointer(m_lfaBase + pDirEntry.Offset);
    case pDirEntry.SubsectionType of
      SUBSECTION_TYPE_MODULE:         AnalyseModules(pSubsection);
      SUBSECTION_TYPE_TYPES:          AnalyseTypes(pSubsection);
      SUBSECTION_TYPE_SYMBOLS:        AnalyseSymbols(pSubsection);
      SUBSECTION_TYPE_ALIGN_SYMBOLS:  AnalyseAlignSymbols(pSubsection, pDirEntry.Size);
      SUBSECTION_TYPE_SOURCE_MODULE:  AnalyseSourceModules(pSubsection);
      SUBSECTION_TYPE_GLOBAL_SYMBOLS: AnalyseGlobalSymbols(pSubsection);
      SUBSECTION_TYPE_GLOBAL_TYPES:   AnalyseGlobalTypes(pSubsection);
      SUBSECTION_TYPE_NAMES:          AnalyseNames(pSubsection);
    else
      OutputDebugString(PChar(Format('Unknown Subsection Type 0x%x!', [pDirEntry.SubsectionType])));
    end;
  end;
  AnalyseDependence;
end;
function TDebugInfomation.OffsetToModule(const AOffset: DWord): TModule;
var
  I: Integer;
begin
  for I := 0 to ModuleCount - 1 do
    if Module[I].IsContainOffset(AOffset) then
    begin
      Result := Module[I];
      Exit;
    end;
  Result := nil;
end;
function TDebugInfomation.OffsetToProc(const AOffset: DWord): TSymbolProc;
var
  AMod: TModule;
begin
  AMod := OffsetToModule(AOffset);
  if Assigned(AMod) then
    Result := AMod.OffsetToProc(AOffset)
  else
    Result := nil;
end;
function TDebugInfomation.OffsetToLineAddress(const AOffset: DWord): TLineAddress;
var
  AMod: TModule;
begin
  AMod := OffsetToModule(AOffset);
  if Assigned(AMod) and Assigned(AMod.SourceFile) then
    Result := AMod.SourceFile.OffsetToLineAddress(AOffset)
  else
    Result := nil;
end;
function TDebugInfomation.OffsetToLineNo(const AOffset: DWord): Integer;
var
  ALineAddr: TLineAddress;
begin
  ALineAddr := OffsetToLineAddress(AOffset);
  if Assigned(ALineAddr) then
    Result := ALineAddr.LineNo
  else
    Result := -1;
end;
end.
 
问题搞定了,多谢tseug。
 
后退
顶部