Hello everyone,
Nim beginner here with a question about shared libraries and C binding.
I have been looking into the code source of db_connector, like this one: https://github.com/nim-lang/db_connector/blob/devel/src/db_connector/postgres.nim
I am trying (to simplify) to create a Nim program which will support checking a variety of databases. The use case would be an organization using the program for only 1 or maybe 2 types of DB (mysql/postgres let's say). If the program ends up supporting 10 types of DB, it would be problematic for an organization to install all 10 shared libraries on their machines.
I have been looking into the pragmas for c binding, like dynlib, and found this doc: https://nim-lang.org/2.2.2/dynlib.html . Most precisely the loadLibPattern. My idea was to use this and check for a return being nil and based on it call a specific DB function or not.
I made a basic project here: https://github.com/hnb2/nim-dynload which uses mysql and postgres. The compilation works, but when running the program on a system without the mysql libs, we get the following:
could not load: (libmysqlclient|libmariadbclient).so(|.20|.19|.18|.17|.16|.15)
(compile with -d:nimDebugDlOpen for more information)
Which is technically correct, the libs are not here at all, but is there a way to just catch it in the code and make the appropriate switches ?
I hope this is somewhat clear, please let me know if this is typically not the way to go in Nim (some sort of compilation flag ?).
PS: I used a github repo because I think its easier to go through the code, but i can copy/paste the code here instead.
Thank you,
The problem is that you still use the db_connector library directly:
from db_connector/db_mysql import DbConn, open, close
And it doesn't support dynamic loading in the sense you are after. You need to fork and patch this dependency or find a package that has done that. I remember it existing somewhere but I might be wrong.
Hi Araq, thanks for your message.
I'd be interested in trying to patch this library, what kind of change would be required to achieve this dynamic loading ?
Because when I change mysql.nim to this:
from db_connector/db_mysql import DbConn, open, close
proc connectMysql*(): bool =
var db: DbConn
#try:
# db = open("localhost:3306", "test","test", "test")
# return true
#except CatchableError as e:
# return false
#finally:
# db.close()
return false
Then nimble run is working. So I assume its in the way the functions (at least, here open) are defined ?
So I ended up doing of mix of the example in dynlib' s doc and db_connector like so:
# Some imports, setting libName based on the OS and other types definitions...
type
pqconnectdb= proc (conninfo: cstring): PPGConn {.gcsafe, stdcall.}
pqstatus = proc (conn: PPGConn): ConnStatusType {.gcsafe, stdcall.}
proc checkLib*(): bool =
# Returns True if the lib can be loaded
let lib = loadLib(libName)
lib != nil
proc connect*(connectionString: string): PPGConn =
let lib = loadLib(libName)
let connect = cast[pqconnectdb](lib.symAddr("PQconnectdb"))
let status = cast[pqstatus](lib.symAddr("PQstatus"))
# TODO: We should have an exception here
let conn = connect(connectionString)
if status(conn) != CONNECTION_OK:
return nil
return conn
And then using like so:
let check = checkLib()
if check:
echo "The lib is loaded"
else:
echo "The lib is NOT loaded"
let conn = connect("postgresql://opaala:opaala@localhost:5432/opaala?connect_timeout=10")
if conn != nil:
echo "it works"
And this "works" the way I was explaining in my first post. If the libs are not installed, it will just display "The lib is NOT loaded" and I can define an exit/ignore routine in my program.
So my question is: Is this the right way to go ? Looks like a more "manual" works than going the pragma way like in the postgres lib. Not necessarily against it, just wondering if i'm in the right direction.
Bonus question: When am I suppose to call unloadLib ? Like, if I want to make a nim function for: PQconninfo, PQconninfoFree, and so on. For evey one of them should I load the lib, use it and unload it ?
Thank you