Examples here: https://gitlab.com/peter-row/pyNimFFI/tree/master/example
It can bind ints, cstrings, floats, and opaque nim references (through a "PyClass" macro). It also wraps sequences of all the above, and propagates exceptions.
Under the hood, it does horrible things with macros, converters, templates, etc (I'm pretty new to nim), and then writes out a json, which my Python script / lib can then parse, and create fake modules, functions, and classes. It can only receive Python lists (converted into seqs in nim) of a max length of 1000 (I couldn't figure out how to use unchecked arrays in cffi, and couldn't figure out how to build a nim seq in Python).
For opaque refs, Python creates a wrapper class, which will call gc unref when the Python object is deleted (so gc works - no leaks I can find).
Missing - dicts and tuples. Basically, you can't get a rich data-type back, just an opaque pointer (which you can then call nim methods on, but there's probably some overhead. No real tests, either, just a big example that tests most of the features. I haven't considered threads, or Python callbacks.
Performance - not very good, as there's a fair bit of Python code in the wrapper. It can run 5-10 times faster for loooong functions, but is much slower for short ones (especially with the overhead of Python-call for exception wrap, python call for every argument that needs conversion, etc).
I also saw that someone else is making a similar project (which will probably end up better), but this one is here if anyone finds it useful.
I mostly see it being useful for Python programmers who want to re-write some bits in nim, and need a rapid prototyping solution.
Sounds like a very cool project! :-)
GC_unref MUST be paired with GC_ref. Note: I didn't check your code but from your description it looks like that you don't do this.
As far as I can tell, when you hand a ref out of nim, via exportc, nim does not de-ref it. From the generated C, it looks like it's assuming any (nim ) caller that obtains a ref is responsible for the GC_unref, so external callers must also make an unpaired GC_unref.
I checked. Pairing my GC_unref with a GC_ref led to memory leaks.
OK, I've added it to the wrapping code. I guess the gc was just being a little conservative, and I mistook it for a leak.
Stress test is using about twice as much memory, but not creeping up over time. I guess my original stress test wasn't long enough to let the gc kick in (it was just a few big objects - the new stress test is a big loop of big objects).