I’ve dedicated the entire weekend to heterogeneous yet related tasks. First, I started off by trying to build NagiQ, our first published game, on OS X Mavericks. By “build” I mean to produce a binary complying with the requirements of the Mac App Store. You know what I mean: well-formed directory hierarchies inside the .app, proper icons and .plists, code signing, etc. For the record, NagiQ is a word game created with Ren’Py, a visual novel engine which is, in turn, built with Python. At the time of release a lot of folks shared their thought regarding our election of Ren’Py to create our word game: it was, to say the least, an unorthodox choice. However, almost 3 years after its initial release, NagiQ is still running fairly well on Windows, Mac and Linux, thanks to the wonderful capabilities of Ren’Py for multi-platform deployment.
Building NagiQ on Mavericks is easy, it amounts to just a single click on a Ren’Py option. However, turning the generated .app into a binary suitable for the Mac App Store has proven to be a daunting task. You have to organize the directory structure of the Python Framework distributed with the game. You have to circumvent the writing of Python .o files in the .app directory, a big no-no for sandboxed apps. You have to retrieve the proper directory to save user and game data (
~/Library/Application Support/NagiQ is not allowed). Etcetera. Right on the middle of such etcetera lies the requirement of communication with a few dynamic libraries needed by NagiQ to satisfy several functional demands.
As you surely know, dynamic libraries = dylibs on Mac. Taking into account that Ren’Py is a citizen of the Python world, we use the
ctypes library to communicate with our dylibs from inside the game. An important lesson I learned during this weekend is that you have to be very careful when dealing with dylibs and
ctypes. First, you have to verify that your dylib and the Python version you’re running are compatible. Is your dylib a 32 or a 64-bits binary? Your Python instance must be apt to properly load and call functions of your dylib, or you will spend a lot of time trying to sort out segmentation faults.
python on a terminal of my Mac launches the 64-bit version of the interpreter by default. If you want to execute the 32-bit version, run this in your shell before launching python:
After you have launched the proper version of Python, you can
import ctypes to load and communicate with your dylib. However, don’t take this communication lightly. Pay special attention to the type of the arguments and return values of your functions. For instance, if you’re invoking a function of your dylib which requires a
char* value, then you have to wrap your argument in the type
c_char_p. Let’s see another example. Suppose you want to get the proper location to save the data of your game. Of course, as a good programmer, you don’t want to hardcode such path. Instead, you’ll be asking the operating system for it, the right way. Let’s create a tiny, demonstrative Objective-C library (demolib.m) for this:
const char* findAppDir()
NSArray *paths = NSSearchPathForDirectoriesInDomains(
NSString *basePath = [paths objectAtIndex:0];
const char *convertedPath = [basePath UTF8String];
clang -dynamiclib -framework Foundation demolib.m -o demolib.dylib
Then you can call your function from Python:
Python 2.7.5 (default, Mar 9 2014, 22:15:05)
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import ctypes
>>> lib = ctypes.cdll.LoadLibrary("demolib.dylib")
>>> findAppDir = lib.findAppDir
>>> findAppDir.restype = ctypes.c_char_p
This little function will prove useful later, when I resume the building of NagiQ. However, the weekend is already over and I have yet to implement several adjustments for DragonScales, our next game soon to be released. The delight of working with dylibs, sandboxes, etc., will surely be the subject of future happy weekends.