Embedding Python in Objective-C: Part 2Virgil Dupras2012-01-20 Two years ago, I wrote about how I embedded Python in Objective-C through PyObjC.
It worked well and everything was fine. The only problem I had with PyObjC was that I felt that
such a massive library ( Then, a few weeks ago, I started getting report of weird crashes which seemed related to 64-bit and the presence on the system of some 3rd party app with specific settings. I could never reproduce the damn crashes on my computer, but they came around exactly at the same moment as dupeGuru 3.3.0 which updated PyObjC to fix another 64-bit related crash, so I could only assume that PyObjC (and/or libffi, which is used by PyObjC) was the cause. The problem was that I didn't know where to start the bug hunt because of my inability to work within PyObjC's codebase. So, out of curiosity for its feasability and to know if it was going to fix my crashes, I started working on ObjP. Instead of dynamically dispatching function call between Python and Objective-C, I would generate source code for static wrappers which would simply use Python's C API. Early tests were encouraging because it fixed the crashes on my users' machines, so I continued working on it and a few days ago I completed dupeGuru's conversion from PyObjC to ObjP and published dupeGuru 3.3.2. So far I haven't heard from crashes, so I guess it works well. What is ObjP?ObjP is a code generation utility. It generates Objective-C code that bridges Python and
Objective-C together using Python's (3.2+) C API. There are two types of wrappers it can generate:
Python-->Objective-C wrappers (p2o), or Objective-C-->Python wrappers (o2p). To generate an class MyClass: def hello_(self, name: str) -> str: return "Hello {}".format(name) This would generate an Objective-C class with this interface: @interface MyClass : NSObject {} - (NSString *)hello:(NSString *)name; @end For The number of data structure it supports is limited ( How to use ObjP?The technicalities are a bit cumbersome to lay out, so I'm going to stay at a higher level of explanations, but you can always look at dupeGuru's code for a real life example. For a simpler example, there's guiskel which I converted to ObjP. The bridging unit So if you want to create an Objective-C app that embeds Python using ObjP, you're going to have to
start by creating a python bridging unit very similar to what you'd do
if you used PyObjC. The difference is that
instead of using Generating o2p code Once you have that bridging unit, you can generate your code using import objp.o2p from bridgeunit import Class1, Class2 objp.o2p.generate_objc_code(Class1, 'autogen') objp.o2p.generate_objc_code(Class2, 'autogen') Now you're going to have - (BOOL)canMoveRows:(NSArray *)rows to:(NSInteger)position { OBJP_LOCKGIL; PyObject *pFunc = PyObject_GetAttrString(py, "canMoveRows_to_"); OBJP_ERRCHECK(pFunc); PyObject *prows = ObjP_list_o2p(rows); PyObject *pposition = ObjP_int_o2p(position); PyObject *pResult = PyObject_CallFunctionObjArgs(pFunc, prows, pposition, NULL); Py_DECREF(prows); Py_DECREF(pposition); OBJP_ERRCHECK(pResult); Py_DECREF(pFunc); BOOL result = ObjP_bool_p2o(pResult); Py_DECREF(pResult); OBJP_UNLOCKGIL; return result; } Now, you can't use them in your Objective-C app yet because it didn't initialize Python. Yup, you have to embed Python in your XCode project. Python in XCode The first thing you need is a python dylib that can be embedded. For this, you need to copy your
own python dylib and update its install name with Once you have that dylib, you can add it in your XCode project, make sure that you link to it and
that it's copied in your Next is Python headers. You're going to have to make sure that XCode can find Python's headers.
Personally, I use Collecting dependencies For your app to run on another machine, you're going to have to collect dependencies for your
Python code and bundle it with your app. You can use Initializing Python Before you can instantiate #import <Cocoa/Cocoa.h> #import <Python.h> #import <wchar.h> #import <locale.h> int main(int argc, char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; /* We have to set the locate to UTF8 for mbstowcs() to correctly convert non-ascii chars in paths */ setlocale(LC_ALL, "en_US.UTF-8"); NSString *respath = [[NSBundle mainBundle] resourcePath]; NSString *mainpy = [respath stringByAppendingPathComponent:@"bridgeunit.py"]; wchar_t wPythonPath[PATH_MAX+1]; NSString *pypath = [respath stringByAppendingPathComponent:@"py"]; mbstowcs(wPythonPath, [pypath fileSystemRepresentation], PATH_MAX+1); Py_SetPath(wPythonPath); Py_SetPythonHome(wPythonPath); Py_Initialize(); PyEval_InitThreads(); PyGILState_STATE gilState = PyGILState_Ensure(); FILE* fp = fopen([mainpy UTF8String], "r"); PyRun_SimpleFile(fp, [mainpy UTF8String]); fclose(fp); PyGILState_Release(gilState); if (gilState == PyGILState_LOCKED) { PyThreadState_Swap(NULL); PyEval_ReleaseLock(); } int result = NSApplicationMain(argc, (const char **) argv); Py_Finalize(); [pool release]; return result; } Besides the basic python initialization calls, you can notice the use of Reaping the rewards After you've done all this, you can finally access your Python code through your ObjP generated
wrappers. Just call Beyond the basics In my own code, I stretch ObjP's capabilities a bit further than what I've exposed there (it
involves passing |
|