I've recently found that it's possible to register a callback to a dynamically loaded library and invoke it.
For example:
# test0.nim
# nim c --app:lib --noMain test0.nim
var registered: proc(p: ptr int) {.nimcall.}
proc registerFn(fn: proc(p: ptr int) {.nimcall.}) {.exportc, dynlib, nimcall.} =
registered = fn
proc invokeWith(p: ptr int) {.exportc, dynlib, nimcall.} =
registered(p)
# test1.nim
proc initModuleTest0() {.importc: "NimMain", cdecl, dynlib: "./libtest0.so".}
proc registerFn(fn: proc(p: ptr int) {.nimcall.}) {.importc, nimcall, dynlib: "./libtest0.so".}
proc invokeWith(p: ptr int) {.importc, nimcall, dynlib: "./libtest0.so".}
proc f(p: ptr int) =
inc(p[])
var x = 0
initModuleTest0()
registerFn(f)
invokeWith(x.addr)
echo x
(I know such code currently have multi-GC problem with refc, because I'm not using -d:useNimrtl and both the main program and the library have GCs.)
Anyway, I think the main program can serve as the role nimrtl usually has, by registering GC-related callbacks to libraries being loaded, using this technique, and thus avoid to have multi-GC.
As ORC has become the default strategy and doesn't have the multi-GC problem, such problem has become less important, I think.