Hi,
With below code I'm getting a strange error with no stack trace. Any suggestions?
app.nim
import dynlib
type
Value* = ref object
P* = proc(): Value {.nimcall.}
when isMainModule:
var lib = loadLib("libext.dylib")
var test: P = cast[P](lib.symAddr("test"))
echo test() != nil
ext.nim
import ./app
proc test*(): Value {.dynlib exportc.} =
Value()
$ nim -v
Nim Compiler Version 1.4.6 [MacOSX: amd64]
Compiled at 2021-05-22
Copyright (c) 2006-2020 by Andreas Rumpf
active boot switches: -d:release
$ nim c --app:lib --outdir:. ext.nim
$ nim c -r app.nim
CC: stdlib_system.nim
CC: app.nim
Hint: /Users/gcao/proj/gene/tmp/bin/app [Exec]
true
No stack traceback available
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
Error: execution of an external program failed: '/Users/gcao/proj/gene/tmp/bin/app '
It is probably caused by ORC. I have this in config.nims
switch("d", "release")
switch("d", "ssl")
switch("gc", "orc")
switch("outdir", "bin")
First of all, at least on Linux the system doesn't search for the shared library in the current directory, so I had to patch the code to test it. Second of all, you need GC_ref in test so that, when destroying from the main app, the reference counts becomes 0 and the ref actually gets deallocated. Check this code:
# app.nim
import std/[os, dynlib]
type
Value* = ref object
P* = proc(): Value {.nimcall.}
when isMainModule:
var lib = loadLib(getCurrentDir() / "libext.so")
if isNil(lib):
quit("Failed to load libext.dynlib!")
var test: P = cast[P](lib.symAddr("test"))
if isNil(test):
quit("Failed to load test proc")
let res = test()
echo "Is the result nil - ", isNil(res)
# ext.nim
import ./app
proc test*(): Value {.exportc, dynlib.} =
result = Value()
GC_ref(result)
It seems GC_ref doesn't help. If you use ORC, it'll still fail.
$ nim c --gc:orc --app:lib --outdir:. ext.nim
Hint: used config file '/Users/gcao/.choosenim/toolchains/nim-1.6.0/config/nim.cfg' [Conf]
Hint: used config file '/Users/gcao/.choosenim/toolchains/nim-1.6.0/config/config.nims' [Conf]
..............................................................................
/private/tmp/app.nim(1, 8) Warning: imported and not used: 'dynlib' [UnusedImport]
Hint: [Link]
Hint: gc: orc; opt: none (DEBUG BUILD, `-d:release` generates faster code)
40615 lines; 0.821s; 60.824MiB peakmem; proj: /private/tmp/ext.nim; out: /private/tmp/libext.dylib [SuccessX]
$ nim c --gc:orc -r app.nim
Hint: used config file '/Users/gcao/.choosenim/toolchains/nim-1.6.0/config/nim.cfg' [Conf]
Hint: used config file '/Users/gcao/.choosenim/toolchains/nim-1.6.0/config/config.nims' [Conf]
.............................................................................
Hint: [Link]
Hint: gc: orc; opt: none (DEBUG BUILD, `-d:release` generates faster code)
40608 lines; 0.797s; 60.742MiB peakmem; proj: /private/tmp/app.nim; out: /private/tmp/app [SuccessX]
Hint: /private/tmp/app [Exec]
true
No stack traceback available
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
Error: execution of an external program failed: '/private/tmp/app '
You really need to understand what you're doing if you're going to be moving GC'd memory across DLL boundaries like this. You have to know when to GC_ref and GC_unref because otherwise memory you try to access might already have been collected by the GC - that or you'll cause leaks by not decrementing ref counters when you should, which will cause allocations not to get collected.
I've written a plugin architecture based game engine (or at least I am in the process of finishing it, as I've written several iterations in the form of R&D projects for my game). If you're interested in seeing how I handle plugins and hot code reloading, you can check out my repository here: https://github.com/zacharycarter/FRAG
A couple of things to note:
https://docs.microsoft.com/en-us/cpp/cpp/allocate?view=msvc-160 | https://www.keil.com/support/man/docs/armcc/armcc_chr1359124982450.htm (I know it's ARM documentation, but the idea idea is the same. gcc docs didn't have a link to that specific section, so I used these docs instead)
This is how state gets preserved across DLL reloads.
Good luck!