Pretty much all of the examples I've seen (docs, searching GitHub, etc) of calling things in external shared libraries from Nim are using a const for the shared library name. Using that will go through whatever search facilities that the OS provides for finding the shared library. That all works quite well.
What I've been trying to do recently is dynamically set the name (the location really) of the shared library. The docs show an example of calculating which DLL to use with some simple checks of whether a particular version of the shared library exists, but I've been stuck on trying to adapt that.
To make this more concrete, the specific problem I'm looking to solve is replacing some py2exe based stuff with Nim, with NimBorg providing the Python integration. For py2exe, you typically don't want the OS getting involved in searching for the python shared library; you want to use the python version that you bundle with your application.
If I update the NimBorg source to use the fully qualified path, that works properly, but I'd prefer to just make NimBorg allow someone to specify a path for the shared library before importing the NimBorg code, and fall back to the existing loading if nothing was specified (so any existing code using NimBorg today would be unaffected by this).
The way that I tried doing that was to introduce a new module in NimBorg that would have procs for getPythonDllName() : String and setPythonDllName(pythonDLL : string). That would allow a caller to import just this utiliy module, call setPythonDllName and then the NimBorg code would call getPythonDllName().
That compiles successfully, but fails at runtime for some reason. I can clean up my test code and post that if anyone is interested, but in the meantime I'm curious if anyone has example code that does something similar that they can point me to.
Thanks
# python_settings is new module that just holds global var for what
# python dll to use and get/setPythonDllName() proc to read/write
# nimborg.py.low_level references that in its {.importc, dynlib: getPythonDllName() .} pragmas
import nimborg.py.python_settings
setPythonDllName( "some/calculated/path/here" )
# now import nimborg like normal
import nimborg.py.high_level
So the reason it was failing before was because the global hadn't been initialized when the import of nimborg.py.high_level is resolved. Putting in a default value so that it is at least initialized makes it stop failing, but still doesn't allow the dynamic calculation of the location.
After realizing that, I re-read the dynlib doc and realized that setting globals and expecting dynlib to be able to read them is essentially the same as passing variables to the dynlib pragma, which the doc explains you shouldn't do.
Note: Passing variables to the dynlib pragma will fail at runtime because of order of initialization problems.
So I think that the answer for now is to go ahead and keep this separate python_settings module for NimBorg, but wrap the import in low_level.nim in logic that allows overriding what gets imported. That will keep existing NimBorg code working, but allow people to override the logic for finding the Python shared library by providing a custom module that implements their logic.
This is what I'm currently testing and it seems to provide the desired functionality.
In low_level.nim
when defined(override_python_settings):
import override_python_settings
else:
import python_settings
The python_settings.nim file has this original logic that was in low_level.nim. Note that we apply the {.compileTime.} pragma to this original logic so that we keep the built-in versioning support.
proc getPythonDllName*() : string {.compileTime.} =
when defined(windows):
# the new buffer protocol only exists since python 2.6
result = "python(27|26).dll"
elif defined(macosx):
result = "libpython(2.7|2.6).dylib"
else:
const dllver = ".1"
result = "libpython(2.7|2.6).so" & dllver
Then in your own code that is using NimBorg, you can define an override_python_settings.nim
import os
proc getPythonDllName*() : string =
var
pluginsDir = getPluginsDirectory() # implementation of this is up to you..
pythonDll = "python27.dll"
result = joinPath(pluginsDir, pythonDll)
Then compile with -d:override_python_settings to get your overrides to take effect.
That seems to work for my purposes so far. I will test a bit more and then get this into the main NimBorg code so that others can use.
I committed this to my fork of NimBorg.
https://github.com/chrisheller/NimBorg/commit/67917453beebcd5a578b904266fd768d0fb7b957
I'm not completely happy with it, but it is at least working for some of my test cases. If there is anyone else with py2exe (or related "freeze" apps) things that they are interested in converting to Nim, take a look.
Pull requests / general feedback are welcome.