Read/Parse PDB file to get symbol details lazarus / 34-Windows ---------------------------------------------------------------------- tooknox · 2026-05-13 12:11 Hi Everyone, Greetings!! I am trying to extract/parse/read symbol info from MS PDB debug files (using fpc ofc). The idea is to specify the PDB file path and the symbol name to dump all the info related to it. I found MS Debug Help library dbghelp.dll, but that looks steep is there anything else I can try to simplify this? Thanks in advance ---------------------------------------------------------------------- 440bx · 2026-05-13 15:10 Disclaimer: I knew the answer to your question but figured I'd let A.I provide the details because there are quite a few worth mentioning.  That means what follows, unlike this sentence and the previous sentence, is A.I generated: To dump the contents of a Microsoft Program Database (PDB) file in C, the standard approach is to use the Microsoft Debug Interface Access (DIA) SDK, which provides a COM-based API for querying symbols and debugging information. 1. Use the DIA SDK (Official Method) The DIA SDK is the most reliable way to access PDB data because it abstracts the underlying file format, which Microsoft frequently changes. Acquire Data Source: Create an IDiaDataSource interface via CoCreateInstance using CLSID_DiaSource.Load the PDB: Use the loadDataFromPdb method to open your specific .pdb file.Open a Session: Call openSession to get an IDiaSession, which serves as your main entry point for queries.Enumerate Symbols: Retrieve the global scope via get_globalScope and use findChildren to iterate through functions, types, and other symbols. Microsoft provides a comprehensive built-in example called DIA2Dump in the Visual Studio installation directory (typically under DIA SDK\Samples\DIA2Dump) that demonstrates exactly how to dump every section of a PDB file. 2. Alternative Tools and Libraries If you want to avoid writing a full COM application, you can use existing utilities or libraries: PDBDump: A tool that can dump PDB contents to JSON, XML, or SQLite3 formats for easier processing.pdbex: A utility specifically designed to reconstruct C headers from PDB files, which is useful if your goal is to see structure and union definitions.RawPDB: A C++11 library for directly reading PDB files if you need low-level access without the DIA SDK overhead. No longer A.I text.  the formatting is mine, not A.I's, A.I would have probably done a better job but, it left out a utility that also accesses the PDB called cv2pdb which converts CodeView info found in the PDB into DWARF.  It uses DIA, you may find it instructive for what you're trying to do (in addition to the ones mentioned above whose source is also available - in C or C++ of course.) I am not aware of a Pascal implementation of .pdb "massaging" or dumping. HTH. [/list] ---------------------------------------------------------------------- tooknox · 2026-05-13 15:38 MS DIA SDK seems promising because of the COM based API, shouldn't it be possble to call the COM based API from freepascal I mean as long as I am just using a few API functions and declare them properly?? In short should I attempt this in pascal? ---------------------------------------------------------------------- 440bx · 2026-05-13 16:32 Quote from: tooknox on May 13, 2026, 05:38:09 pmIn short should I attempt this in pascal? I cannot think of a reason why not.  Usually, COM Pascal code is cleaner than its C++ counterpart (and no comparison with C's.) I don't know if there is a port of the DIA COM api... that, you'll have to find out. ---------------------------------------------------------------------- cdbc · 2026-05-13 17:19 Hi Yes, you should definitely use DIA com-interfaces, it's sorta like programming with classes ~ Object-Oriented, once you get the hang of it, it's prolly way easier than you think  Regards Benny ---------------------------------------------------------------------- tooknox · 2026-05-14 07:22 @cdbc You were right about it being just objects and methods, I was able to successfully use the msdia.dll com library (still have a few questions at the end though) Here is what I did, 1. Find the path of msdia140.dll in the msvc installation/DIA SDK folder and register it with regsvr32.dll Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i regsvr32 \VC\Tools\MSVC\14.50.35717\bin\Hostx64\x64\msdia140.dll 2. Use the FPC importtl utility to generate the pascal unit for the COM DLL. https://wiki.freepascal.org/Windows_Programming_Tips#COM_Programming 3. Then this sample code can get for eg the RVA of a symbol in PDB Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program msdiacom;uses  Windows, ComObj, ActiveX, SysUtils, MSDIA140_TLB; // generated MSDIA140_TLB.pas containing declarations using importtl utilityconst  // NameSearchOptions Constants from Microsoft DIA SDK (dia2.h)  nsNone               = 0;  nsfCaseSensitive     = 1;  nsfCaseInsensitive   = 2;  nsfFNameExt          = 4;  nsfRegularExpression = 8;  nsfUndecoratedName   = 16; procedure GetSymbolRVA(const PdbPath, SymbolName: WideString);var  DataSource: IDiaDataSource;  Session: IDiaSession;  GlobalScope: IDiaSymbol;  EnumSymbols: IDiaEnumSymbols;  Symbol: IDiaSymbol;  RVA: QWORD;  Fetched: ULONG;begin  //RVA := 0;  CoInitialize(nil);  try     DataSource := CreateComObject(CLASS_DiaSource) as IDiaDataSource;     if DataSource.loadDataFromPdb(PWideChar(PdbPath)) <> S_OK then      raise Exception.Create('Failed to load PDB');     DataSource.openSession(Session);     GlobalScope := Session.get_globalScope;     GlobalScope.findChildren(SymTagFunction, PWideChar(SymbolName), nsNone, EnumSymbols);     if EnumSymbols.Next(1, Symbol, Fetched) = S_OK then    begin        RVA := Symbol.get_relativeVirtualAddress;        WriteLn(Symbol.Get_name, ' RVA: ', IntToHex(RVA));    end;   finally    CoUninitialize;  end;end; begin  GetSymbolRVA('C:\test.pdb', 'main');end.   However, I banged my head for a few hours when I got access violation errors. I had to change the calling convention for get_relativeVirtualAddress function in the importtl generated pas file from stdcall to safecall. Why didn't it work with stdcall which is technically the correct call conv?? and any way to make importtl utility correct it instead of manual workaround?? Attaching the importtl generated pas file for reference. ---------------------------------------------------------------------- Thaddy · 2026-05-14 08:19 This is quite simple, but for basic pdb parsing it is sufficient. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit PdbParser;{ courtesy codex, May 14, 2026, Thaddy de Koning }{$mode objfpc}{$H+} interface uses  Classes, SysUtils; type  EPdbError = class(Exception);   TPdbInfo = record    Version: DWord;    Signature: DWord;    Age: DWord;    Guid: TGuid;  end;   TPdbStreamInfo = record    Size: Int64;    Blocks: array of DWord;  end;   TPdbDbiInfo = record    VersionSignature: LongInt;    VersionHeader: DWord;    Age: DWord;    GlobalSymbolStream: SmallInt;    PublicSymbolStream: SmallInt;    SymbolRecordStream: SmallInt;  end;   TPdbPublicSymbol = record    Name: string;    Segment: Word;    Offset: DWord;    Flags: DWord;  end;   TPdbPublicSymbolArray = array of TPdbPublicSymbol;   { TPdbFile parses the MSF container used by Microsoft PDB files.    It is intentionally small: it reads the stream directory, PDB info stream,    DBI stream indices, and simple CodeView S_PUB32/S_PUB32_ST records. }  TPdbFile = class  private    FData: TBytes;    FBlockSize: DWord;    FBlockCount: DWord;    FDirectorySize: DWord;    FBlockMapAddress: DWord;    FStreams: array of TPdbStreamInfo;    function ReadUInt16(const Offset: Int64): Word;    function ReadInt16(const Offset: Int64): SmallInt;    function ReadUInt32(const Offset: Int64): DWord;    function ReadInt32(const Offset: Int64): LongInt;    procedure CheckRange(const Offset, Count: Int64);    procedure ParseSuperBlock;    procedure ParseStreamDirectory;    function StreamBlockCount(const StreamIndex: Integer): Integer;  public    constructor Create(const FileName: string);    function StreamCount: Integer;    function StreamSize(const StreamIndex: Integer): Int64;    function ReadStream(const StreamIndex: Integer): TBytes;    function TryReadPdbInfo(out Info: TPdbInfo): Boolean;    function TryReadDbiInfo(out Info: TPdbDbiInfo): Boolean;    function ReadPublicSymbols: TPdbPublicSymbolArray;    property BlockSize: DWord read FBlockSize;    property BlockCount: DWord read FBlockCount;    property DirectorySize: DWord read FDirectorySize;    property BlockMapAddress: DWord read FBlockMapAddress;  end; implementation const  PdbSignature: AnsiString = 'Microsoft C/C++ MSF 7.00';  StreamPdb = 1;  StreamDbi = 3;  NilStreamSize = DWord($FFFFFFFF);   S_PUB32_ST = Word($1009);  S_PUB32 = Word($110E); function AlignUp(Value, Alignment: Int64): Int64;begin  if Alignment <= 0 then    raise EPdbError.Create('Invalid alignment');  Result := ((Value + Alignment - 1) div Alignment) * Alignment;end; function BytesToAnsiString(const Data: TBytes; Offset, Count: Integer): string;var  I: Integer;begin  SetLength(Result, Count);  for I := 0 to Count - 1 do    Result[I + 1] := AnsiChar(Data[Offset + I]);end; constructor TPdbFile.Create(const FileName: string);var  S: TFileStream;begin  inherited Create;  S := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);  try    SetLength(FData, S.Size);    if S.Size > 0 then      S.ReadBuffer(FData[0], S.Size);  finally    S.Free;  end;  ParseSuperBlock;  ParseStreamDirectory;end; procedure TPdbFile.CheckRange(const Offset, Count: Int64);begin  if (Offset < 0) or (Count < 0) or (Offset + Count > Length(FData)) then    raise EPdbError.CreateFmt('PDB read outside file at offset %d, size %d', [Offset, Count]);end; function TPdbFile.ReadUInt16(const Offset: Int64): Word;begin  CheckRange(Offset, SizeOf(Result));  Move(FData[Offset], Result, SizeOf(Result));end; function TPdbFile.ReadInt16(const Offset: Int64): SmallInt;begin  CheckRange(Offset, SizeOf(Result));  Move(FData[Offset], Result, SizeOf(Result));end; function TPdbFile.ReadUInt32(const Offset: Int64): DWord;begin  CheckRange(Offset, SizeOf(Result));  Move(FData[Offset], Result, SizeOf(Result));end; function TPdbFile.ReadInt32(const Offset: Int64): LongInt;begin  CheckRange(Offset, SizeOf(Result));  Move(FData[Offset], Result, SizeOf(Result));end; procedure TPdbFile.ParseSuperBlock;var  I: Integer;begin  CheckRange(0, 56);  for I := 1 to Length(PdbSignature) do    if AnsiChar(FData[I - 1]) <> PdbSignature[I] then      raise EPdbError.Create('Not an MSF 7.0 PDB file');   FBlockSize := ReadUInt32(32);  FBlockCount := ReadUInt32(40);  FDirectorySize := ReadUInt32(44);  FBlockMapAddress := ReadUInt32(52);   if (FBlockSize = 0) or (FBlockSize > 1024 * 1024) then    raise EPdbError.CreateFmt('Unsupported PDB block size %d', [FBlockSize]);  if Int64(FBlockCount) * FBlockSize > Length(FData) then    raise EPdbError.Create('PDB block table extends past the file size');  if FBlockMapAddress >= FBlockCount then    raise EPdbError.Create('Invalid PDB stream directory block map address');end; procedure TPdbFile.ParseStreamDirectory;var  DirBlockCount, I, J, Pos, StreamCountValue, BlockCountValue: Integer;  MapOffset, DirOffset: Int64;  DirBlocks: array of DWord;  DirData: TBytes;   function DirReadUInt32: DWord;  begin    if Pos + 4 > Length(DirData) then      raise EPdbError.Create('Truncated PDB stream directory');    Move(DirData[Pos], Result, 4);    Inc(Pos, 4);  end; begin  DirBlockCount := AlignUp(FDirectorySize, FBlockSize) div FBlockSize;  SetLength(DirBlocks, DirBlockCount);  MapOffset := Int64(FBlockMapAddress) * FBlockSize;  CheckRange(MapOffset, Int64(DirBlockCount) * SizeOf(DWord));   for I := 0 to DirBlockCount - 1 do  begin    DirBlocks[I] := ReadUInt32(MapOffset + I * SizeOf(DWord));    if DirBlocks[I] >= FBlockCount then      raise EPdbError.Create('Invalid stream directory block number');  end;   SetLength(DirData, DirBlockCount * FBlockSize);  for I := 0 to DirBlockCount - 1 do  begin    DirOffset := Int64(DirBlocks[I]) * FBlockSize;    CheckRange(DirOffset, FBlockSize);    Move(FData[DirOffset], DirData[I * FBlockSize], FBlockSize);  end;  SetLength(DirData, FDirectorySize);   Pos := 0;  StreamCountValue := DirReadUInt32;  if StreamCountValue < 0 then    raise EPdbError.Create('Invalid stream count');  SetLength(FStreams, StreamCountValue);   for I := 0 to StreamCountValue - 1 do    FStreams[I].Size := DirReadUInt32;   for I := 0 to StreamCountValue - 1 do  begin    if DWord(FStreams[I].Size) = NilStreamSize then    begin      FStreams[I].Size := -1;      Continue;    end;     BlockCountValue := AlignUp(FStreams[I].Size, FBlockSize) div FBlockSize;    SetLength(FStreams[I].Blocks, BlockCountValue);    for J := 0 to BlockCountValue - 1 do    begin      FStreams[I].Blocks[J] := DirReadUInt32;      if FStreams[I].Blocks[J] >= FBlockCount then      raise EPdbError.CreateFmt('Invalid block number for stream %d', [I]);    end;  end;end; function TPdbFile.StreamBlockCount(const StreamIndex: Integer): Integer;begin  Result := Length(FStreams[StreamIndex].Blocks);end; function TPdbFile.StreamCount: Integer;begin  Result := Length(FStreams);end; function TPdbFile.StreamSize(const StreamIndex: Integer): Int64;begin  if (StreamIndex < 0) or (StreamIndex >= StreamCount) then    raise EPdbError.CreateFmt('Invalid PDB stream index %d', [StreamIndex]);  Result := FStreams[StreamIndex].Size;end; function TPdbFile.ReadStream(const StreamIndex: Integer): TBytes;var  I: Integer;  CopyCount, Remaining: Int64;  SourceOffset, DestOffset: Int64;begin  if (StreamIndex < 0) or (StreamIndex >= StreamCount) then    raise EPdbError.CreateFmt('Invalid PDB stream index %d', [StreamIndex]);  if FStreams[StreamIndex].Size < 0 then    raise EPdbError.CreateFmt('PDB stream %d is not present', [StreamIndex]);   SetLength(Result, FStreams[StreamIndex].Size);  Remaining := Length(Result);  DestOffset := 0;  for I := 0 to StreamBlockCount(StreamIndex) - 1 do  begin    CopyCount := FBlockSize;    if CopyCount > Remaining then      CopyCount := Remaining;    SourceOffset := Int64(FStreams[StreamIndex].Blocks[I]) * FBlockSize;    CheckRange(SourceOffset, CopyCount);    if CopyCount > 0 then      Move(FData[SourceOffset], Result[DestOffset], CopyCount);    Inc(DestOffset, CopyCount);    Dec(Remaining, CopyCount);  end;end; function TPdbFile.TryReadPdbInfo(out Info: TPdbInfo): Boolean;var  Data: TBytes;begin  FillChar(Info, SizeOf(Info), 0);  Result := (StreamPdb < StreamCount) and (StreamSize(StreamPdb) >= 24);  if not Result then    Exit;   Data := ReadStream(StreamPdb);  Move(Data[0], Info.Version, SizeOf(Info.Version));  Move(Data[4], Info.Signature, SizeOf(Info.Signature));  Move(Data[8], Info.Age, SizeOf(Info.Age));  Move(Data[12], Info.Guid, SizeOf(Info.Guid));end; function TPdbFile.TryReadDbiInfo(out Info: TPdbDbiInfo): Boolean;var  Data: TBytes;begin  FillChar(Info, SizeOf(Info), 0);  Result := (StreamDbi < StreamCount) and (StreamSize(StreamDbi) >= 20);  if not Result then    Exit;   Data := ReadStream(StreamDbi);  Move(Data[0], Info.VersionSignature, SizeOf(Info.VersionSignature));  Move(Data[4], Info.VersionHeader, SizeOf(Info.VersionHeader));  Move(Data[8], Info.Age, SizeOf(Info.Age));  Move(Data[12], Info.GlobalSymbolStream, SizeOf(Info.GlobalSymbolStream));  Move(Data[14], Info.PublicSymbolStream, SizeOf(Info.PublicSymbolStream));  Move(Data[16], Info.SymbolRecordStream, SizeOf(Info.SymbolRecordStream));end; function TPdbFile.ReadPublicSymbols: TPdbPublicSymbolArray;var  Dbi: TPdbDbiInfo;  Data: TBytes;  Pos, RecordStart, NameStart, NameEnd, Count: Integer;  RecordLength, RecordKind: Word;  Symbol: TPdbPublicSymbol;   procedure AddSymbol(const Value: TPdbPublicSymbol);  begin    if Count = Length(Result) then      SetLength(Result, Count + 64);    Result[Count] := Value;    Inc(Count);  end; begin  SetLength(Result, 0);  Count := 0;  if not TryReadDbiInfo(Dbi) then    Exit;  if (Dbi.SymbolRecordStream < 0) or (Dbi.SymbolRecordStream >= StreamCount) then    Exit;  if StreamSize(Dbi.SymbolRecordStream) <= 0 then    Exit;   Data := ReadStream(Dbi.SymbolRecordStream);  Pos := 0;  while Pos + 4 <= Length(Data) do  begin    Move(Data[Pos], RecordLength, SizeOf(RecordLength));    RecordStart := Pos + 2;    if (RecordLength < 2) or (RecordStart + RecordLength > Length(Data)) then      Break;     Move(Data[RecordStart], RecordKind, SizeOf(RecordKind));    if ((RecordKind = S_PUB32) or (RecordKind = S_PUB32_ST)) and (RecordLength >= 14) then    begin      Move(Data[RecordStart + 2], Symbol.Flags, SizeOf(Symbol.Flags));      Move(Data[RecordStart + 6], Symbol.Offset, SizeOf(Symbol.Offset));      Move(Data[RecordStart + 10], Symbol.Segment, SizeOf(Symbol.Segment));      NameStart := RecordStart + 12;      NameEnd := NameStart;      while (NameEnd < RecordStart + RecordLength) and (Data[NameEnd] <> 0) do        Inc(NameEnd);      Symbol.Name := BytesToAnsiString(Data, NameStart, NameEnd - NameStart);      AddSymbol(Symbol);    end;     Pos := RecordStart + AlignUp(RecordLength, 4);  end;  SetLength(Result, Count);end; end. PDBDump:Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program PdbDump; {$mode objfpc}{$H+} uses  SysUtils, PdbParser; var  Pdb: TPdbFile;  Info: TPdbInfo;  Dbi: TPdbDbiInfo;  Symbols: TPdbPublicSymbolArray;  I, Limit: Integer; begin  if ParamCount <> 1 then  begin    Writeln('Usage: pdbdump ');    Halt(2);  end;   try    Pdb := TPdbFile.Create(ParamStr(1));    try      Writeln('PDB MSF 7.0');      Writeln('Block size: ', Pdb.BlockSize);    Writeln('Block count: ', Pdb.BlockCount);      Writeln('Directory size: ', Pdb.DirectorySize);      Writeln('Streams: ', Pdb.StreamCount);       for I := 0 to Pdb.StreamCount - 1 do        Writeln('  #', I:3, ' size=', Pdb.StreamSize(I));       if Pdb.TryReadPdbInfo(Info) then      begin        Writeln;        Writeln('PDB info:');        Writeln('  Version: ', Info.Version);      Writeln('  Signature: ', Info.Signature);        Writeln('  Age: ', Info.Age);        Writeln('  GUID: ', GuidToString(Info.Guid));      end;       if Pdb.TryReadDbiInfo(Dbi) then      begin        Writeln;        Writeln('DBI info:');        Writeln('  Version signature: ', Dbi.VersionSignature);        Writeln('  Version header: ', Dbi.VersionHeader);        Writeln('  Age: ', Dbi.Age);        Writeln('  Global symbol stream: ', Dbi.GlobalSymbolStream);        Writeln('  Public symbol stream: ', Dbi.PublicSymbolStream);        Writeln('  Symbol record stream: ', Dbi.SymbolRecordStream);      end;       Symbols := Pdb.ReadPublicSymbols;      Writeln;      Writeln('Public symbols found: ', Length(Symbols));      Limit := Length(Symbols);      if Limit > 50 then        Limit := 50;      for I := 0 to Limit - 1 do        Writeln('  ', Symbols[I].Segment, ':', HexStr(Symbols[I].Offset, 8), ' ', Symbols[I].Name);      if Length(Symbols) > Limit then        Writeln('  ...');    finally      Pdb.Free;    end;  except    on E: Exception do    begin      Writeln(StdErr, E.ClassName, ': ', E.Message);      Halt(1);    end;  end;end.As I wrote: it is intentionally simple. This could be the basis of an IDE plugin. I am happy to do that if required. ---------------------------------------------------------------------- tooknox · 2026-05-14 10:25 @Thaddy, thanks for the raw implementation. Will test it out, looks like it will work just fine Also just to update on the MS DIA COM library, I figured it out, the issue is importtl.exe generates correct declarations for [al]most [all] of the functions except those that return HRESULT and take a pointer to a variable to store output. eg. all C++ signatures like HRESULT get_relativeVirtualAddress(DWORD* pRetVal); get converted to function Get_relativeVirtualAddress: LongWord; stdcall; which would ofcourse cause stack issue, adding safecall fixes it. I verified (manually), the issue is only with all the Get_xxx functions. This single sed should fix all of them and we have a nice pascal wrapper for msdia com library. (ofcourse there still may be some corner case) Code: Bash  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i sed -E -i.bak '/^[[:space:]]*function[[:space:]]+Get_[A-Za-z_][A-Za-z0- 9_]*[[:space:]]*:[[:space:]]*HResult[[:space:]]*;[[:space:]]*stdcall[[ :space:]]*;/! s/^([[:space:]]*function[[:space:]]+Get_[A-Za-z_][A-Za- z0- 9_]*[[:space:]]*:[[:space:]]*[^;]+;[[:space:]]*)stdcall([[:space:]]*;[ [:space:]]*)$/\1safecall\2/I' MSDIA140_TLB.pas  Attaching the modified pascal wrapper here, in case someone wants to test things out. Some constants might seem missing but those come from dia header file, I have included a few in my example and others are mostly integers you can pass directly. or will attach them later. Edit: Added the constants, don't know what they all do but they sure are there now. ---------------------------------------------------------------------- Thaddy · 2026-05-14 13:59 (Note in my version you can change all occurrences of "SmallInt" to "Word" You can also ignore possible warnings that that gives you) ---------------------------------------------------------------------- marcov · 2026-05-14 15:26 Importtl operates on the COM interface description in the DLL, not the direct C++ prototype. It could be that the importtl unit ignores some atttribute that signals the safecall signature, or the TLB is simply wrong and the C++ prototype was manually fixed. Probably it needs debugging to see what is what. I tried to do so, and I have two dozen instances of that DLL on my system, but they won't register (dllregisterserver failed with 0x80070005) with regsvr32 (even in automation directories) ---------------------------------------------------------------------- Thaddy · 2026-05-14 16:29 Run regsvr32 as administrator. I also note CreateComObject is used (late bound) instead of the expected CoCreateInstance or CoCreateInstanceEx (early bound). That is either / or: these are different models. If you want to use late bound, the whole shebang of initializing COM and using tlb's is not necessary. You can just as well use CreateOleObject... ---------------------------------------------------------------------- tooknox · 2026-05-14 17:05 @marcov, Did some testing and it seems only the function which take a pointer to out var as the ONLY argument are affected. For eg, the functions like these are declared correctly Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function openSession(out ppSession:IDiaSession):HRESULT;stdcall; but a function that takes say pointer to int as argument to store the result gets declared as a function returning Int with no argument. Moreover since all of these functions are getter, there is a corresponding property which will again cause access violation. All other types of methods/functions get declared perfectly. I used the msdia140.dll distributed with MSVC toolchain and ran regsvr32 as admin. @Thaddy thanks for pointing out the early/late binding. I just happen to land on the only freepascal wiki page regarding COM DLL and importtl felt convenient with everything properly defined. CreateComObject came after I kept getting errors and tried things in desparation  Eitherway would be great to have importtl fixed (if it's a bug/issue) ---------------------------------------------------------------------- marcov · 2026-05-14 21:45 Quote from: tooknox on May 14, 2026, 07:05:09 pm@marcov, Did some testing and it seems only the function which take a pointer to out var as the ONLY argument are affected. For eg, the functions like these are declared correctly Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function openSession(out ppSession:IDiaSession):HRESULT;stdcall; I looked at IDiaSymbol::relativeVirtualAddress with a typelib viewer and this is what it says: Quote[id(0x0000000a), propget, helpstring(".")] HRESULT relativeVirtualAddress([out, retval] unsigned long* pRetVal); So it has "out" and "retval" as properties, as well as a HRESULT returnvalue, which IMHO is a sign that the TLB is ok.  But maybe because it is not a function, but a property, somehow that transformation goes wrong. The relevant code for properties seems to be way shorter than for functions (around line importtl: 805) Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i if FD^.invkind=INVOKE_PROPERTYGET then            begin            s:=s+format('   function Get_%s%s : %s; %s;'#13#10,[sMethodName,sPropParam2,sType,sConv]);            with aPropertyDefs[findProperty(FD^.memid)] do              begin            bget:=true;              name:=sMethodName;              sgtype:=sType;              sorgname:=BstrName;              sdoc:=BstrDocString;              sParam:=sPropParam;              sDefault:=sl;              end;     Which has no handling for those attributes, which would involve some checking in FD^.lprgelemdescParam[0].paramdesc.wParamFlags and PARAMFLAG_FREFVAL and PARAMFLAG_FOUT. The code would probably look like somewhere around line 585: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  if ((FD^.lprgelemdescParam[FD^.cParams-1].paramdesc.wParamFlags and (PARAMFLAG_FRETVAL or PARAMFLAG_FOUT)) <>0) then          begin        delete(sType,1,1); //out parameters are always defined as pointer        if assigned(FD^.lprgelemdescParam[FD^.cParams-1].tdesc.lptdesc) then            iType:=FD^.lprgelemdescParam[FD^.cParams-1].tdesc.lptdesc^.vt;        end;     But that is all my uneducated guess of course. It might be worth filing a bug. ---------------------------------------------------------------------- tooknox · 2026-05-15 05:07 @marcov You are absolutely correct, I got some basic understanding of importtl code (with help of AI) and I think a something basic like this should fix it. The checks can be/should be done around the lines you mentioned but just for simplicity I have added extra functions. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function IsOutRetValParam(AIndex: Integer): Boolean;var  Flags: Word;begin  Result := False;   if FD = nil then    Exit;   if AIndex < 0 then    Exit;   if AIndex >= FD^.cParams then    Exit;   Flags := FD^.lprgelemdescParam[AIndex].paramdesc.wParamFlags;   Result :=    ((Flags and PARAMFLAG_FOUT) <> 0) and    ((Flags and PARAMFLAG_FRETVAL) <> 0);end; function PropertyGetNeedsSafecall: Boolean;begin  Result := False;   if FD = nil then    Exit;   if FD^.invkind <> INVOKE_PROPERTYGET then    Exit;   if FD^.callconv <> CC_STDCALL then    Exit;   // Dispatch and dual interfaces already use safecall in importtl.  // Line 607-610 already have this  // if not (bIsDispatch or ((TA^.wTypeFlags and TYPEFLAG_FDUAL)=TYPEFLAG_FDUAL)) then  //      sConv:='stdcall'  //    else  //      sConv:='safecall';   if bIsDispatch then    Exit;   if (TA^.wTypeFlags and TYPEFLAG_FDUAL) <> 0 then    Exit;   // Need at least one parameter so there can be a retval parameter.  if FD^.cParams <= 0 then    Exit;      // the retval would be the final parameter.  if not IsOutRetValParam(FD^.cParams - 1) then    Exit;   Result := True;end; // And then have INVOKE_PROPERTYGET check the extra conditionif FD^.invkind = INVOKE_PROPERTYGET thenbegin  if PropertyGetNeedsSafecall then    s := s + format(' function Get_%s%s : %s; safecall;'#13#10,                    [sMethodName, sPropParam2, sType])  else    s := s + format(' function Get_%s%s : %s; %s;'#13#10,                    [sMethodName, sPropParam2, sType, sConv]);  This is definitely a bug cuz I see safecall getting handled for other conditions just fine. BTW before I open a bug, this would go in main FPC-source work items right? ---------------------------------------------------------------------- marcov · 2026-05-15 09:50 Quote from: tooknox on May 15, 2026, 07:07:32 am@marcov You are absolutely correct, I got some basic understanding of importtl code (with help of AI) and I think a something basic like this should fix it. The checks can be/should be done around the lines you mentioned but just for simplicity I have added extra functions. ... This is definitely a bug cuz I see safecall getting handled for other conditions just fine. BTW before I open a bug, this would go in main FPC-source work items right? I also think it is a bug, but it is maybe a rare situation, where a property getting/setter is not a pure getter/setter. I don't know how we are supposed to deal with that (keep in mind if you change the signature of the getter function, then "property" might no longer work. And we can't use some wrapper function because it is an interface. (helper?). If it can deal with the safecall form, that would work though. I tried to import using Delphi, but it crashed. ---------------------------------------------------------------------- tooknox · 2026-05-16 07:57 @marcov Created the issue https://gitlab.com/freepascal.org/fpc/source/-/work_items/41765 ---------------------------------------------------------------------- LeP · 2026-05-16 14:12 In replay to @Marcov (as note) in gitlab regarded to issue (I don't have acces sto it), safecall working as getter / setter property in Delphi. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     .......    function Get_Visible: WordBool; safecall;    procedure Set_Visible(Value: WordBool); safecall;    ........    property Visible: WordBool read Get_Visible write Set_Visible;  EDIT: how is described in RIDL Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     [propget, id(0x000000C9)]    HRESULT _stdcall Visible([out, retval] VARIANT_BOOL* Value);    [propput, id(0x000000C9)]    HRESULT _stdcall Visible([in] VARIANT_BOOL Value); ---------------------------------------------------------------------- marcov · 2026-05-31 20:33 I committed an initial fix to trunk, please test. ---------------------------------------------------------------------- tooknox · 2026-06-05 17:36 Hey marcov, That's awesome  Had a tough week at work, will test and report back in some time ---------------------------------------------------------------------- tooknox · 2026-06-06 08:01 Just tested it works flawlessly. All the getters are properly declared now. Saw your commit too with bFoldHResult, good work !!  Thanks for pushing the change this quick, didn't expect it. ---------------------------------------------------------------------- marcov · 2026-06-06 12:48 Great that it works. I'll see if I can merge it to fixes.