After my last post outlining an “Abort” issued by Python when attempting to load a shared module, I realized I had made some pretty basic errors in my implementation. The gist of the issue is that I linked my shared module to the static Python runtime library in order to resolve dependencies. When I called some Python C API routines, they were not calling the appropriate procedures within the Python process. The explanation above, however, does require a brief overview of the Atari’s shared library architecture.
A shared library built on the Atari using the LDG method is not strictly a library, but, rather, a highly specialized executable. If you look at the source code I’ve provided below, you’ll notice that the library code includes a main() function. When an LDG file is executed, it normally issues a message saying that it is a shared library.
The original LDG design was basically a one-directional shared library. The process that loads the shared library can call into the shared library, but the reverse was not possible. This functionality is problematic when using a system as complicated as CPython because many calls will affect global interpreter information, such as the garbage collection system or the namespace. If my shared library needs to call a Python C API function, it needs to call the appropriate function within the executable that loaded the shared library. The two processes (client and it’s shared library) are not strictly sharing memory, so the shared library will be performing operations only within its own memory space. The SIGABRT was most likely issued because I attempted to initialize a Python module within a process address space that had absolutely no initialization of a Python interpreter.
The most recent version of the LDG library provides a workaround for this one-directional shortcoming. The library implements ldg_callback(), which allows the shared library to call back into the originating process. The problem is that ldg_callback() must be provided with a function pointer. To use this callback functionality with the Python C API, the Python process would have to provide a complete list of function addresses to the shared library.
This solution isn’t particularly practical for general implementation. The Python C API is substantial, so passing all the functions might be a bit troublesome. While implementing the solution is certainly possible, it might take quite some time.
I did, however, go back to generate at least a skeleton of how things would need to work. Rather than just leaping into initializing a module in a shared library, the MiNT implementation first loads the library and passes an array of function addresses into the shared library for use. The shared library, meanwhile, contains re-definitions of necessary functions via preprocessor macros to call ldg_callback() with the proper entry in the array of function pointers. The whole thing is a bit unwieldy, but…
The shared gem library does, in fact, work. I was able to call appl_init(), followed by form_alert(), followed finally by appl_exit(). There continue to be some minor issues, namely that the version information that the shared library supplies to Python is clearly incorrect. Although the shared library implementation is somewhat impractical, I believe I’ll continue to work within a GEM shared library for the remainder of this challenge if not solely to take advantage of the significantly faster turnaround when adding features as compared to rebuilding Python. Here’s a screenshot for some proof of progress:

If anyone feels like looking, I’ve again included some source code:
- dynload_mint.c – The most minimal implementation necessary to get my demo code running
- gem.c – A functioning GEM module with three whole functions!