Embed the Python data stack inside a Delphi VCL desktop app — no separate Python install on the end-user machine.
This repository is a working starter that wires together:
- a Delphi 12 VCL host application
- Python4Delphi (P4D) for in-process Python
- the official Python 3.11.9 embeddable runtime (x64)
- a small pandas processing module that returns JSON
Delphi owns the UI and process lifetime. Python does the analytics. Communication is a simple, typed-enough contract: pass a path (or arguments), get JSON back.
- Why this exists
- Features
- Architecture
- Repository layout
- Requirements
- Quick start
- Embeddable Python setup (detailed)
- Delphi IDE configuration
- Running the demo
- How the bridge works
- Python API contract
- Extending the project
- Deployment notes
- Submodule management
- Development workflow
- Troubleshooting
- Security considerations
- Limitations
- Roadmap
- License & credits
Delphi is excellent for native Windows desktop UIs. Python is excellent for pandas, NumPy, and scientific/ML workflows. Combining them in production usually forces one of these bad options:
| Approach | Problem |
|---|---|
| Install full Python on every PC | Fragile, version conflicts, IT friction |
Shell out with CreateProcess |
Process orchestration, path hell, poor UX |
| Rewrite analytics in Pascal | Slow, loses the Python ecosystem |
This template embeds CPython in-process via P4D and the official embeddable distribution. You keep a familiar Delphi EXE workflow and call Python like a library.
Typical use cases
- Point-cloud / CSV / tabular processing inside a desktop tool
- Prototyping data science logic in Python while shipping a VCL UI
- Reusing existing pandas pipelines from a Delphi host
- Shipping analytics without requiring a system-wide Python install
- In-process Python — loads
python311.dllfrom a project-local embeddable runtime - No system Python required on target machines (only the redistributables you ship)
- Clean separation —
app_delphi(UI/host) vsapp_py(processing) - JSON bridge — Delphi calls
main(path)and receives a JSON string - P4D + VarPyth — natural
Import('main')/ method call style from Pascal - Git submodules — Python4Delphi and embeddable Python versioned with the repo
- Sample dataset — XYZ point-cloud CSV to exercise the happy path
- MIT licensed application code (dependencies keep their own licenses)
┌─────────────────────────────────────────────────────────────┐
│ DelphiApp.exe (VCL) │
│ │
│ MainForm │
│ └─ button click │
│ └─ Import('main') ──VarPyth──► app_py/main.py │
│ └─ main(path) ◄── JSON ──┘ │
│ │
│ PyEngineService (singleton) │
│ • resolve project root from EXE path │
│ • DllName / PythonHome → embeddable Python folder │
│ • SetDllDirectory │
│ • LoadDll │
│ • sys.path ← app_py │
└───────────────────────────┬─────────────────────────────────┘
│ loads
▼
external_libraries/python-3.11.9-embed-amd64
(python311.dll + site-packages: pandas, numpy)
Request / response flow
- User clicks Run Python Processing (or you call the same code from your form).
- Delphi reads an optional path from the memo (first line); otherwise uses
data/input/sample_points.txt. PyEngine.EnsureReadyconfirms the Python engine handle is valid.Import('main')loadsapp_py/main.py.main(path)runs pandas, builds a resultdict, and returnsjson.dumps(...).- Delphi displays the JSON string (or you parse it with
System.JSON).
.
├── README.md
├── LICENSE # MIT
├── .gitignore
├── .gitmodules
│
├── app_delphi/ # Delphi VCL host
│ ├── DelphiApp.dpr # Program entry
│ ├── DelphiApp.dproj # IDE project
│ ├── DelphiApp.res
│ ├── forms/
│ │ ├── MainForm.pas # Demo UI + Python call
│ │ └── MainForm.dfm
│ └── services/
│ └── PyEngineService.pas # Embeddable Python bootstrap
│
├── app_py/ # Python processing side
│ ├── main.py # Entry point called from Delphi
│ └── requirements.txt # Packages for the embeddable runtime
│
├── data/
│ └── input/
│ └── sample_points.txt # Sample XYZ CSV (header: x,y,z)
│
└── external_libraries/ # Git submodules (not vendored as copies)
├── python4delphi/ # https://github.com/pyscripter/python4delphi
└── python-3.11.9-embed-amd64/ # Embeddable CPython 3.11.9 (x64)
Build outputs (Win64/, __history/, .dcu, etc.) are gitignored and must not be committed.
| Component | Notes |
|---|---|
| OS | Windows x64 |
| Delphi | 12 recommended (Community or higher), VCL |
| Platform | Win64 (matches the embeddable amd64 runtime) |
| Git | Required for submodules |
| VC++ Redistributable | Needed on machines that run the embeddable CPython build |
| Disk | Room for submodules + pip packages inside the embeddable tree |
This project targets Win64 only with the provided amd64 embeddable Python. Win32 would need a matching 32-bit runtime and project changes.
git clone --recurse-submodules https://github.com/juandapradam12/DelphiPythonBridge.git
cd DelphiPythonBridgeIf you cloned without submodules:
git submodule update --init --recursiveVerify:
git submodule status
dir external_libraries\python-3.11.9-embed-amd64\python311.dll
dir external_libraries\python4delphi\SourceSee Embeddable Python setup (detailed). Short version (after pip works):
cd external_libraries\python-3.11.9-embed-amd64
python.exe -m pip install -r ..\..\app_py\requirements.txt- Open
app_delphi\DelphiApp.dproj. - Set platform to Win64.
- Add P4D library paths (see Delphi IDE configuration).
- Build + Run.
- Click Run Python Processing.
external_libraries\python-3.11.9-embed-amd64\python.exe app_py\main.py data\input\sample_points.txtThe official Windows embeddable package is intentionally minimal. Out of the box it often cannot import pip or third-party packages until you enable site and install pip once.
In external_libraries\python-3.11.9-embed-amd64\python311._pth (filename may vary slightly by build), ensure import site is uncommented, for example:
python311.zip
.
import site
Without this, packages installed under Lib\site-packages may be invisible.
If python.exe -m pip fails:
- Download
get-pip.py. - Run it with the embeddable interpreter:
cd external_libraries\python-3.11.9-embed-amd64
python.exe get-pip.pycd external_libraries\python-3.11.9-embed-amd64
python.exe -m pip install -r ..\..\app_py\requirements.txt
python.exe -m pip show pandas numpyCurrent app_py/requirements.txt:
numpy>=1.26
pandas>=2.1
Important: always install into this embeddable interpreter. Packages on a system/global Python will not be seen by the Delphi-hosted runtime.
python.exe -c "import pandas, numpy; print(pandas.__version__, numpy.__version__)"-
Open
app_delphi\DelphiApp.dprojin the Delphi IDE. -
Project → Options → Delphi Compiler → Target platform:
Windows 64-bit. -
Add to Library path (and Search path if you prefer):
$(PROJECTDIR)\..\external_libraries\python4delphi\Source$(PROJECTDIR)\..\external_libraries\python4delphi\Source\vcl
Absolute paths also work if relative macros are awkward in your IDE version.
-
Confirm these units resolve:
PythonEngine,VarPyth. -
Build (Project → Build DelphiApp).
PyEngineService is created in the unit initialization section, so Python is bootstrapped when the app starts (not only on button click).
- Start
DelphiApp. - You should see
App initializedin the memo. - Optionally type a file path on the first line of the memo.
- Click Run Python Processing.
- JSON output is appended below.
If the first memo line is empty, the default path is:
data/input/sample_points.txt
That path is resolved from the repository root on the Python side (see API section).
{
"status": "ok",
"path": "C:\\...\\data\\input\\sample_points.txt",
"rows": 97,
"cols": 3,
"columns": ["x", "y", "z"],
"first_row": {
"x": 381.3919131502,
"y": 430.2502623372,
"z": 109.0358264945
}
}PyEngineService.ProjectRootFromExe assumes the usual Delphi output layout:
<repo>/app_delphi/Win64/Debug/DelphiApp.exe
^ ^ ^
+3 parents = <repo>
If you change output directories, update that helper or Python will fail to find python311.dll / app_py.
Responsibilities:
| Step | What it does |
|---|---|
| Resolve root | Walk up from EXE dir to repo root |
| Locate runtime | external_libraries\python-3.11.9-embed-amd64\python311.dll |
| Configure engine | UseLastKnownVersion := False, set DllName + PythonHome |
| Help Windows | SetDllDirectory on the embeddable folder |
| Load | FPython.LoadDll |
| Import path | Insert app_py into sys.path |
Key idea: never rely on a machine-wide Python. The DLL path is always project-local.
Minimal UI that demonstrates the call pattern:
PyEngine.EnsureReady;
PyMain := Import('main'); // app_py/main.py
PyRes := PyMain.main(PathStr); // must return something VarPyth can convert
Memo1.Lines.Add(VarToStr(PyRes));Errors from Python or missing files are caught and shown in the memo.
| Function | Role |
|---|---|
process_file(path) |
Load CSV/TXT with pandas, return a summary dict |
main(path=None) |
Delphi entry point; always returns a JSON string |
__main__ |
CLI helper for standalone testing |
Relative paths are joined to the repository root (parent of app_py).
Always returns a JSON string.
{
"status": "ok",
"path": "<absolute path>",
"rows": 97,
"cols": 3,
"columns": ["x", "y", "z"],
"first_row": { "x": 0.0, "y": 0.0, "z": 0.0 }
}{
"status": "ok",
"message": "Hello from Python! Session=<uuid>, Time=<iso8601>"
}{
"status": "error",
"message": "File not found: ..."
}or
{
"status": "error",
"message": "Error reading file ...: <ExceptionType>: <details>"
}- Keep
mainstable — Delphi should call one or a few well-known entry points. - Return JSON strings for structured results (easy to log, display, and parse).
- Put failures in JSON when possible; reserve Delphi exceptions for engine/bootstrap failures.
- Avoid GUI / blocking work on the Python side unless you add threading consciously (VCL is single-threaded by default).
- Create modules under
app_py/(e.g.app_py/pipeline.py). - Import them from
main.pyor viaImport('pipeline')from Delphi. - Pin new packages in
requirements.txtand reinstall into the embeddable runtime. - Keep heavy logic in Python; keep UI and file-picker UX in Delphi.
Example Delphi call to another module:
PyPipe := Import('pipeline');
PyRes := PyPipe.run_analysis(PathStr, OptionsJson);Instead of only showing text:
uses System.JSON;
// ...
var
Doc: TJSONValue;
begin
Doc := TJSONObject.ParseJSONValue(VarToStr(PyRes));
try
// read fields...
finally
Doc.Free;
end;
end;- Replace
data/input/sample_points.txt, or - Change the default string in
MainForm.BtnRunClick.
You would need to:
- Swap the embeddable submodule / folder
- Update DLL name (
python3xx.dll) inPyEngineService - Rebuild and retest P4D against that version
Stick to 3.11.x unless you have a reason to move.
To run on a machine without Delphi or a system Python, ship at least:
YourApp.exe
external_libraries/python-3.11.9-embed-amd64/ # full tree, including site-packages
app_py/ # your .py modules
data/ # if required at runtime
Also ensure:
- Folder layout still matches what
ProjectRootFromExeexpects, or you change that function for an installer layout - Visual C++ Redistributable is installed
- You tested on a clean Windows VM
Do not assume pip is available on the customer machine; bake dependencies into the embeddable tree before shipping.
Configured in .gitmodules:
| Path | Upstream |
|---|---|
external_libraries/python4delphi |
https://github.com/pyscripter/python4delphi.git |
external_libraries/python-3.11.9-embed-amd64 |
https://github.com/juandapradam12/PythonEmbeddable-3.11.9.git |
git submodule update --init --recursivegit submodule update --remote external_libraries/python4delphi
git add external_libraries/python4delphi
git commit -m "chore: update Python4Delphi submodule"Always rebuild the Delphi project after updating P4D.
- Edit Python in
app_py/— fast to test via CLI. - Edit Delphi host / UI as needed.
- Run the VCL app for integration tests.
- Commit source only (never
Win64/,__history/,.dcu,.exe).
external_libraries\python-3.11.9-embed-amd64\python.exe app_py\main.py data\input\sample_points.txt- Keep commits focused (Python logic vs Delphi host vs docs)
- Prefer conventional prefixes when useful:
feat:,fix:,docs:,chore: - Re-test the button path after any change to
PyEngineServiceor submodule paths
| Symptom | Likely cause | Fix |
|---|---|---|
Python DLL not found at: ... |
Submodule missing or wrong EXE layout | git submodule update --init --recursive; confirm EXE under app_delphi\Win64\... |
ImportError: No module named pandas |
Deps installed on system Python, or import site disabled |
Install into embeddable runtime; uncomment import site in python311._pth |
pip not found on embeddable Python |
Minimal embeddable layout | Bootstrap with get-pip.py |
Cannot compile PythonEngine / VarPyth |
Library path incomplete | Add P4D Source and Source\vcl |
| App starts then dies on Python init | Bad DLL / wrong bitness / missing VC++ runtime | Confirm Win64 + amd64 DLL; install VC++ Redistributable |
| File not found from Python | Relative path / root resolution | Use repo-relative paths like data/input/... or pass absolute paths |
| Works in IDE, fails when installed elsewhere | Deployed folder layout differs | Adjust ProjectRootFromExe or ship a fixed PythonHome |
| VS Code / Explorer shows “dirty” submodule | Submodule checked out at different commit | Normal — commit intentional submodule bumps only |
- Run
main.pyfrom the CLI first to isolate Python issues. - Log
DllPath,PythonHome, and resolved project root from Delphi if bootstrap fails. - Confirm
python311.dllarchitecture matches the Delphi target (both x64).
This bridge executes Python inside your process. Treat it with the same care as loading a native plugin:
- Do not run untrusted
.pyfiles or user-supplied scripts without a sandbox strategy. - Validate/sanitize file paths coming from the UI.
- Prefer returning structured JSON over executing dynamic code strings from Delphi.
- Keep the embeddable runtime and pip packages updated for CVE fixes.
- Avoid logging secrets; this sample has no credentials, and
.envfiles are gitignored.
Be aware of what this starter does not include yet:
- No automated test suite / CI
- No FMX / cross-platform host (VCL + Win64 only)
- No background thread marshaling helpers for long Python jobs
- No installer project (Inno Setup / MSIX / etc.)
- Sample processing is a CSV summary — not a full domain pipeline
- Embeddable pip bootstrap still requires a one-time manual setup on new machines/clones
These are intentional scope boundaries so the core bridge stays clear and copyable.
Ideas for future iterations:
- Longer-running jobs with cancel + UI thread marshaling
- Richer sample pipelines (filtering, transforms, exports)
- Delphi-side JSON helpers for common result shapes
- Optional installer / portable zip layout docs
- Basic pytest coverage for
app_py - CI that at least lint/tests the Python side
Contributions and issue reports are welcome if you fork or adapt this for your stack.
Application code in this repository is released under the MIT License — see LICENSE.
| Component | Role | License |
|---|---|---|
| Python4Delphi | Delphi ↔ Python integration | See upstream repo |
| CPython embeddable | Runtime | PSF License |
| pandas / NumPy | Sample processing stack | Their respective licenses |
Keep Delphi for the product UI. Keep Python for the data. Let this bridge glue them without fighting installers.