I’d like to experiment with writing a program which can generate GObject bindings for various statically-typed languages. Some of it could be based on ts-for-gjs, but with that I had the advantage that
gjs had already solved this lifecycle management problem so I didn’t have to think about it.
Various strategies spring to mind, and I’m not sure which to choose:
- Wrapper/proxy objects in the target language call
g_object_refwhen initialised and
g_object_unrefwhen destroyed. This is robust, but could lead to GObjects holding on to resources indefinitely while waiting to be GC’d.
- Wrapper/proxy objects hold a weak ref. This avoids resource leaks/high memory consumption etc, but having wrappers outlive the objects they refer to is a problem in case user code tries to access them after the wrapped object has been destroyed.
- Using both techniques and switching between them based on GI transfer semantics, could work, but still wouldn’t fully eliminate the problem above with invalid accesses.
Also, when functions return objects, so a wrapper has to be created for them, should I use a fresh instance of a wrapper every time, or make sure each GObject only has one wrapper, using something like qdata? The pros and cons probably depend on the overhead of creating objects in each target language.
Then there’s Kotlin native which has no destructors, or any other mechanism I know of to notify us when a Kotlin object is GC’d. I think the only way that could be made to work, other than exposing
g_object_unref, is to have wrappers use GObject’s weak references. I think using the single wrapper with qdata would then be best, because now GObjects have to keep their wrappers alive instead of the other way round, to ensure wrappers don’t get GC’d before handling a