Integrate C/C++ libraries into PascalAI via the clib bridge — DLL wrapping, MSVC builds, Delphi bridge, Int64 rules
ppm install clib-integration
ppm skill install-claude
The second command activates the skill in Claude Code
(copies it into .claude/skills/).
You are a clib integration expert for PascalAI. You know the full bridge pipeline from C source to PAI external declarations.
src/pai_libX.c — C source (or .cpp for C++)
↓ cl /LD /O2 /MT
libpai_X.dll — Win64 DLL
↓
PAI.ClibBridge.pas — Delphi native function registrations
↓
ppm/pkgs/X/main.pai — PascalAI unit with external declarations
↓
tests/examples/NNN_clib_X.pai — Demo program
// src/pai_mylib.c
#include <windows.h>
#include <stdlib.h>
#include <string.h>
#define PAI_EXPORT __declspec(dllexport)
// For C++ files: always add extern "C" to avoid name mangling
// PAI_EXPORT extern "C" int pai_func(...) { ... }
PAI_EXPORT int64_t pai_version(void) { return 100; }
PAI_EXPORT double pai_compute(double a, double b) { return a + b; }
// Strings: return malloc'd buffer, caller frees via pai_free
PAI_EXPORT const char* pai_process(const char* input) {
char* result = (char*)malloc(strlen(input) + 64);
sprintf(result, "Processed: %s", input);
return result;
}
PAI_EXPORT void pai_free(void* ptr) { free(ptr); }
@echo off
set LIB_NAME=libpai_mylib
cl /nologo /LD /O2 /MT ^
/I"include" ^
src\pai_mylib.c ^
/Fe%LIB_NAME%.dll ^
/link mylib.lib
copy %LIB_NAME%.dll ..\..\..\compiler\PascalAI\host\bin\
For C++: add /EHsc and /DMSGPACK_NO_BOOST (if needed).
unit mylib;
// CRITICAL: ALWAYS use Int64 for integer returns (NOT Integer — 4B vs 8B → stack corruption)
function pai_version: Int64; external 'libpai_mylib';
function pai_compute(A, B: Double): Double; external 'libpai_mylib';
function pai_process(Input: PChar): PChar; external 'libpai_mylib';
procedure pai_free(Ptr: Pointer); external 'libpai_mylib';
// Registration
procedure LoadMyLibPackage;
begin
Lape.addGlobalFunc('function MyLib_Version: Int64', @LapeMyLib_Version);
Lape.addGlobalFunc('function MyLib_Compute(A, B: Double): Double', @LapeMyLib_Compute);
Lape.addGlobalFunc('function MyLib_Process(Input: string): string', @LapeMyLib_Process);
end;
// Native function — read ALL params FIRST, write Result LAST
procedure LapeMyLib_Compute(const Params: PParamArray; const Result: Pointer);
var A, B: Double;
begin
A := PDouble(Params^[0])^; // read param 0
B := PDouble(Params^[1])^; // read param 1
PDouble(Result)^ := A + B; // write result LAST
end;
// WRONG (4B Integer vs 8B Lape slot → stack corruption)
Lape.addGlobalFunc('function Foo: Integer', @LapeFoo);
// CORRECT
Lape.addGlobalFunc('function Foo: Int64', @LapeFoo);
// WRONG — overwrites Params^[0] when Result = Params (same address)
PDouble(Result)^ := 0.0; // corrupts first param!
A := PDouble(Params^[0])^; // reads garbage
// CORRECT
A := PDouble(Params^[0])^;
PDouble(Result)^ := A * 2;
Without it, C++ name mangling makes functions unfindable at runtime.
If IntToStr(myFunc()) gives "overloaded", the return type isn't registered:
FFuncReturnTypes.AddOrSetValue('mylib_version', 'Int64');
PAI_EXPORT int64_t pai_db_open(const char* path) {
DB* db = db_open(path); return (int64_t)db;
}
PAI_EXPORT void pai_db_close(int64_t handle) { db_close((DB*)handle); }
var H: Int64 := pai_db_open('mydb.db');
// ... use H ...
pai_db_close(H);
// Return delimited string: "field1\x01field2\x01field3"
PAI_EXPORT const char* pai_get_info(int64_t handle) { ... }
| Symptom | Cause | Fix |
|---|---|---|
| Function not found at runtime | C++ name mangling | Add extern "C" to exports |
| Stack corruption / wrong values | Integer size mismatch | Use Int64 not Integer |
| Wrong result from compute func | Param read after Result write | Read all params first |
| Access violation on first call | Missing transitive DLL | Copy dep DLL to host/bin/ |
IntToStr overloaded error |
Return type not in FFuncReturnTypes | Register in InferLapeType |
| Linker error: unresolved external | Wrong lib name in /link |
Check .lib name from vcpkg |