Hello everyone,
We've been working on a merge request that adds full MSVC/LLVM/MinGW static library support to FPC's internal COFF linker.
https://gitlab.com/freepascal.org/fpc/source/-/merge_requests/1385This allows linking .a and .lib files produced by Clang, GCC, and MSVC (containing C++ template instantiations, COMDAT sections, short import objects, weak externals, CRT initializers) without falling back to an external linker.
We'd appreciate testing from anyone who links C/C++ static libraries into their FPC projects on Windows.
What's new- COMDAT section support, including deduplication, associative sections, and all 6 selection modes. Required for any C++ library with templates or inline functions.
- LLVM/Clang archive compatibility, covering second linker member handling, NUL-terminated long filename entries, alignment padding fixes, and reloc allocation fix.
- MSVC short import object parsing that reads DLL import definitions (0x0000 0xFFFF signature) from .lib archives and generates proper IAT entries. Also fixed __imp_ prefixed symbol matching in GenerateLibraryImports to correctly generate direct IAT pointers instead of jmp thunks, which was causing runtime crashes with MSVC .lib import libraries.
- Duplicate symbol fix for same-name COMDAT sections. LLVM objects can have 1000+ .rdata sections with identical names, and each now gets its own TObjSymbol so relocations resolve correctly.
- Weak externals and /alternatename:X=Y directive, used extensively by MSVC CRT objects for fallback symbol definitions. Added placeholder symbol registration so the archive scanner pulls in members that define /alternatename targets.
- /include:SYMBOL directive support. UCRT objects use this to force-load CRT initializer objects (stdio, heap, locale, etc.).
- /DEFAULTLIB:, /NODEFAULTLIB:, /DISALLOWLIB: directives for automatic loading of dependent libraries from .drectve sections. With these directives supported, adding the Visual Studio library paths to the FPC search paths is all that's needed for the linker to pull in everything automatically. For example:
-Fl"C:\Program Files\Microsoft Visual Studio\18\Enterprise\VC\Tools\MSVC\14.50.35717\lib\x64"
-Fl"C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22621.0\ucrt\x64"
-Fl"C:\Program Files (x86)\Windows Kits\10\Lib\10.0.26100.0\um\x64"
Tested with the latest update of Visual Studio. No need to manually specify each library, only the main ones needed by the project. The linker resolves dependencies from the MSVC objects automatically if defined by /DEFAULTLIB. 
- .CRT$ section handling so MSVC CRT initializer function pointer tables survive section GC and get sorted correctly via $ ordering.
- .lib extension recognized as static archive, meaning {$linklib foo.lib} now works on Windows instead of misrouting to the DLL import path. Also fixed AddStaticCLibrary mangling .lib files to libfoo.lib.a.
- Archive rescan loop with member dedup to handle circular dependencies between libraries (Now you can linklib in any order)
like libucrt.a and libmsvcrt.a.
- __imp_ prefix fallback so the archive scanner finds MSVC short import objects indexed under __imp_ names.
- MSVC section patterns in PE linker script for _RDATA*, .00cfg*, .rtc$*, .fptable*, .gfids$*, .giats$*. Without these, MSVC sections get no ExeSection assignment and all RIP-relative references break.
- RELOC_SECREL32 fix that always subtracts ExeSection.MemPos for section-relative offsets, fixing TLS variable crashes.
- BigObj COFF format support for COFF objects with more than 65535 sections, as produced by large C++ template libraries.
- LTCG anonymous object detection fix. Tightened detection to require mach=0 AND nsects=0 AND syms=0, so machine-independent COFF objects are no longer falsely skipped.
- RELOC_SECTION handler with IMAGE_REL_AMD64_SECTION support for 16-bit section index relocations.
- Relocation handling for discarded COMDATs. Zeroes out relocations to discarded COMDAT sections instead of triggering internalerror.
- symansistr enabled for x86_64 because MSVC C++ mangled symbol names can exceed 500 characters. Enabled ansistring-based symbol storage to prevent silent truncation at 255 chars.
- i386 support with IMAGE_REL_I386_ABSOLUTE and IMAGE_REL_I386_SECTION relocation types including read/write mappings. Added ___ImageBase symbol for the i386 underscore prefix convention. Fixed GetSection erroring on COFF special section indices (-1 absolute, -2 debug).
- FixupSymbols absolute symbol when linking i386 MSVC objects. COFF special section indices: -1 (IMAGE_SYM_ABSOLUTE) for symbols with fixed values not in any section (e.g. __tls_array at TEB offset 0x2C on i386), -2 (IMAGE_SYM_DEBUG) for debug symbols. These are still valid special values.
Important: CRT initialization for C/C++ static librariesIf you are statically linking C/C++ libraries (MSVC or MinGW), the C runtime needs to be initialized before any C code runs. Add this unit to your project and make sure it is in your uses clause:
unit CPPInit;
{$mode ObjFPC}{$H+}
{$Assertions ON}
interface
implementation
{$if defined(WINDOWS) and not defined(MinGW)}
uses windows;
{$EndIf}
{$If defined(MinGW)}
procedure __main(); cdecl; external;
{$ElseIf defined(WINDOWS)} // MSVC
{$IfDef CPU64}
function CRT_INIT(hinstDLL: HINST; fdwReason: DWORD; lpReserved: PtrInt): LongBool; stdcall;
external name '_CRT_INIT';
{$else}
function CRT_INIT(hinstDLL: HINST; fdwReason: DWORD; lpReserved: PtrInt): LongBool; stdcall;
external name '__CRT_INIT@12';
{$EndIf}
{$EndIf}
initialization
{$If defined(MinGW)}
__main();
{$ElseIf defined(WINDOWS)} // MSVC
Assert(CRT_INIT(HINSTANCE, DLL_PROCESS_ATTACH, dllparam), 'CRT_INIT FAILED');
{$EndIf}
{$if defined(WINDOWS) and not defined(MinGW)}
finalization
CRT_INIT(HINSTANCE, DLL_PROCESS_DETACH, dllparam);
{$EndIf}
end.
Without this, you may get crashes in C/C++ library functions that depend on CRT heap, stdio, or locale initialization. Define
MinGW if linking MinGW-compiled libraries, otherwise MSVC mode is used by default.
We are investigating adding this initialization automatically, either through the linker itself or through system.pas, so that users won't need this unit in the future. However, this may require weak external support on Windows targets so the CRT init symbols are only resolved when C libraries are actually linked, without causing errors in pure Pascal projects. For now, the manual unit approach above is the reliable way to handle it.
What needs testingIf you can test any of the following scenarios, it would be really helpful:
- Basic regression test. Build any existing FPC project that does NOT use C libraries. Make sure the internal linker still works as before, on both x86_64 and i386 on Windows.
- Clang/LLVM static libraries. If you have any .a files produced by clang/llvm-ar, try linking them with the internal linker.
- MSVC static libraries. If you have .lib files from MSVC (cl.exe), try {$linklib something.lib} and see if it links.
- GCC/MinGW static libraries. Try .a files from gcc/g++ via the MinGW toolchain.
- Mixed toolchain. Linking libraries from different compilers in the same project MinGW/MSVC/Clang.
- DLL building. If you build DLLs that link against C/C++ static libraries, verify exports and imports are correct.
- Debug info. Verify that DWARF/stabs debug info still works when linking C objects.
- Cross-compilation. If you cross-compile for Win64 from Linux or other hosts.
How to test1. Apply the merge request on top of current main branch
2. Build the compiler (the RTL needs to be rebuilt as well)
3. Build your test projects using the new compiler with the internal linker
4. Report any errors, crashes, or unexpected behavior
If you hit "Unresolved external symbol" errors, please include the full symbol name as it helps identify which feature might need adjustment.
Thanks for any testing you can do!