I called it like this:
procedure TPythonEngine.Execute(const Code: PAnsiChar; const globals, locals: IPyDictionary);
var
retval: PPyObjectRef;
begin
retval := PyRun_StringFlags(Code, Py_file_input, globals.Ref, locals.Ref, nil);
CheckError(Assigned(retval));
DecRef(retval);
end;
I passed exit() in via the Code argument, and empty dicts in globals and locals. retval comes back nil and CheckError is my function to extract tracebacks.
I know we looked at way back when we first embedded Python. I honestly don't recall the details. I think some part of the decision will be my own fastidiousness to have total control over everything, and as you know P4D has a huge range of functionality. We only scratch at the surface of what P4D can do.
Our usage is a finite element structural engineering code which allows users to customise some parts of the calculation and post-processing by calling Python code that they provide. So we just need to create instances, populate attributes, and call the user function passing in those instances. Then we need to read out the attributes from those instances after the user script has run. It's such a tiny part of what P4D offers, that we just did it ourself.
The way our wrapper exposes Python is much closer to the metal than P4D I believe. We use interfaces to wrap Python objects, and because they are also reference counter, that fits really nicely with the Python ref count. When our implementing wrapper objects are destroyed (because the Delphi ref count goes to zero), we just call Py_DecRef on the underlying Python object. I seem to recall that in P4D client code you sometimes have to deal with the Python ref code, but we've been able to hide that from our client code.
Anyway, embedded Python is a truly remarkable achievement, and P4D is clearly a fantastic library. Our software would be far poorer without embedded Python.