Interfacing with C, Inline Assembly & Performance

Or declare it yourself: function MessageBoxW(hWnd: THandle; lpText, lpCaption: PWideChar; uType: Cardinal): Integer; stdcall; external 'user32.dll';

36.1 Calling C library functions

cdecl (C), stdcall/winapi (Windows API) external 'libname' (name 'symbol' if names differ)

ctypes unit: cint, clong, cuint, cdouble; SizeUInt/NativeUInt for size_t; PAnsiChar for char*

record with {$PACKRECORDS C} (FPC) or matching {$ALIGN} (Delphi) pass PAnsiChar(UTF8String(S)); copy results with StrPas or string(P)

Pascal function with cdecl, passed with @ {$L file.o} / {$LINKLIB name} (FPC); {$L file.obj} (Delphi)

h2pas (FPC), Chet (C header translator for Delphi), manual translation

program CallC;
{$mode objfpc}{$H+}
uses ctypes;              // C-compatible types: cint, clong, cuint, cdouble, ...

const
  {$IFDEF WINDOWS}
  LibC = 'msvcrt.dll';
  {$ELSE}
  LibC = 'c';             // libc.so / libSystem on macOS
  {$ENDIF}

function c_strlen(s: PAnsiChar): SizeUInt; cdecl; external LibC name 'strlen';   // size_t
function c_abs(x: cint): cint; cdecl; external LibC name 'abs';

type
  TCompareFunc = function(a, b: Pointer): cint; cdecl;

procedure c_qsort(base: Pointer; num, size: SizeUInt; compar: TCompareFunc);
  cdecl; external LibC name 'qsort';

function CompareInts(a, b: Pointer): cint; cdecl;      // a Pascal callback for C
begin
  Result := PInteger(a)^ - PInteger(b)^;
end;

var
  Data: array[0..5] of Integer = (42, 7, 19, 3, 88, 1);
  I: Integer;
begin
  Writeln(c_strlen('Hello from C'));      // 12
  Writeln(c_abs(-5));                     // 5
  c_qsort(@Data[0], Length(Data), SizeOf(Integer), @CompareInts);
  for I in Data do Write(I, ' ');         // 1 3 7 19 42 88
  Writeln;
end.

36.2 Windows API

Or declare it yourself: function MessageBoxW(hWnd: THandle; lpText, lpCaption: PWideChar; uType: Cardinal): Integer; stdcall; external 'user32.dll';

uses Windows;      // Delphi: Winapi.Windows
begin
  MessageBox(0, 'Hello from the Win32 API', 'Pascal', MB_OK or MB_ICONINFORMATION);
end.

36.3 Dynamic loading

Delphi also supports external 'lib.dll' delayed; to load a library on first use.

uses dynlibs;      // FPC; Delphi: LoadLibrary/GetProcAddress/FreeLibrary from Winapi.Windows or SysUtils (SafeLoadLibrary)
type
  TAddFunc = function(A, B: Integer): Integer; cdecl;
var
  H: TLibHandle;
  Add: TAddFunc;
begin
  H := LoadLibrary('mathlib.' + SharedSuffix);    // SharedSuffix: 'dll', 'so', or 'dylib'
  if H = NilHandle then Halt(1);
  try
    Add := TAddFunc(GetProcedureAddress(H, 'AddInts'));
    if Assigned(Add) then Writeln(Add(2, 3));
  finally
    UnloadLibrary(H);
  end;
end.

36.4 Inline assembly

FPC: {$asmmode intel} or {$asmmode att}; supports x86, x86-64, ARM, AArch64, and other CPUs.

Delphi: Win32 allows asm blocks inside Pascal routines; Win64 allows only routines written entirely in assembler; ARM/mobile/Linux targets have no inline assembler.

Prefer compiler intrinsics and well-written Pascal: modern optimizers are good, and assembly is unportable and hard to maintain. Use it for SIMD hot loops after profiling.

{$mode objfpc}
{$IFDEF CPUX86_64}
{$asmmode intel}
function AddAsm(A, B: LongInt): LongInt; assembler; nostackframe;
asm
  {$IFDEF WINDOWS}
  lea eax, [ecx + edx]      // Win64 ABI: first args in RCX, RDX
  {$ELSE}
  lea eax, [edi + esi]      // System V ABI (Linux, macOS): RDI, RSI
  {$ENDIF}
end;
{$ENDIF}

36.5 Performance checklist

Measure first (GetTickCount64, Delphi TStopwatch, profilers) intuition about hot spots is often wrong

Better algorithms and data structures (TDictionary vs linear search)

const parameters for strings, records, interfaces avoids copies and ref-count traffic

Pre-size: SetLength, TList<T>.Capacity, TStringBuilder avoids repeated reallocation

Avoid Variant, Format and string concatenation in hot loops slow, allocate memory

Records instead of classes for small value objects no heap allocation inline small routines removes call overhead

Release builds: -O3 (FPC), optimization on, {$R-}{$Q-} checks cost time Avoid exceptions for control flow raising is expensive

Memory managers: FastMM (Delphi default); FPC cmem or alternative managers for multithreaded workloads

Profilers: Valgrind/Callgrind, perf (Linux, FPC), Intel VTune, AQtime, Delphi sampling profilers find real bottlenecks

uses SysUtils;
var
  T0: QWord;
  I: Integer;
  S: Double;
begin
  T0 := GetTickCount64;
  S := 0;
  for I := 1 to 50000000 do
    S := S + Sqrt(I);
  Writeln('Sum = ', S:0:0, ' in ', GetTickCount64 - T0, ' ms');
end.