Hello,
Trying to create ldap bindings I found the following questions in my head:
I was trying to make bindings not just to make ptr ptr Ldap or pointer but reduce amount of work to wrap it later
On the C-side:
typedef struct ldap LDAP;
int ldap_initialize(ldp **LDAP, const char *url);
It is pretty standard for C to have double ref to initialize something on pointer
The first idea was to make simple, but wrong because Nim does not expect that ref can be changed
type LdapRef = ref object
proc ldap_initialize(ldp: var LdapRef, url: csring) {.importc.}
var ldp: LdapRef
ldap_initialize(ldp, "url")
But the second idea became more complicate:
type Ldap = object
type LdapInit = object
r: ptr Ldap
proc ldap_initialize(ldp: var LdapInit, url: csring) {.importc.}
var ldpInit: LdapInit
ldap_initialize(ldpInit, "url")
or to remove var and make LdapInit = ref object
Because we own the Ldap structure we are responsible to free it, and I added
proc `=destroy`(x: var BerVal) =
ldap_free x.r # the same like simple free
The question is that the second way looks +- ok, but a bit overcomplicated for the simple problem.
I think that I found answer overnight:
low level FFI just with ptr, but a bit higher level looks the ptr and do free if necessary The main point was not to pass high level value, but pointer for it, so the final result is:
type LdapInt = object
type Ldap = object
r: ptr Ldap
proc `=destroy`(x: var Ldap) =
ldap_memfree x.r
proc ldap_initialize(ldp: ptr LdapInit, url: cstring) {.importc.}
var ld: Ldap
ldap_initialize(ld.r, "url")
Is it possible to make it without additional LdapInit structure but without ptr ptr also?
Yes, you can use addr. Looks like this is what they do in C according to stackoverflow. Something like this:
ldap_initialize(addr(ld), "url")
Is it possible to own the ptr from Nim to make the destructor unnecessary and Nim will free it automatically?
No, Nim won't know how to free the memory. The ldap_memfree may end up freeing sub-elements first. Not calling it likely would result in a memory leaks or worse.
What is the best practices for it? Because the problem looks pretty trivial
It's fairly straightforward, but it's best practice to add an idiomatic Nim layer on top to automate the low level C parts.
I'd recommend returning a ref object so you only deallocate once. Otherwise you can end up copying Ldap and destroying them, which can result in calling ldap_memfree multiple times.
Here's an example:
type
LdapRaw {.importc: "LDAP", header: "ldap.h", bycopy.} = object
Ldap* = object
raw: ptr LdapRaw
LdapRef* = ref Ldap
proc ldap_initialize(ldp: ptr LdapRaw, url: cstring) {.importc.}
proc ldap_memfree(ldp: LdapRaw) {.importc.}
proc `=destroy`*(x: var Ldap) =
echo "freeing ldap object: ", repr x
ldap_memfree(x.raw)
proc newLdap*(url: string): LdapRef =
new(result) # allocate the container for LdapRaw
ldap_initialize(addr result.raw, url.cstring)
block:
let ldap = newLdap("some string")
echo "ldap: ", repr ldap
# should print "freeing ..." here
echo "done"
Thank you @elcritch
Can I another question:
// C: f(char *attrs[])
proc f(attrs: ptr cstring) {.importc.}
var attrs = ["1".cstring]
f(attrs.addr) // or attrs[0].addr => both fails
Glad to help!
update: FIXED: it required NULL-terminated array what could be the best way to pass pointer of cstrings/strings array
There's a helper in the stdlib type for arrays of C strings: https://nim-lang.org/docs/system.html#cstringArray
If you look in the system module there's other helpers for C string arrays like: https://nim-lang.org/docs/system.html#cstringArrayToSeq%2CcstringArray
but I cannot return it from the function. I can cast of course, but not sure is it good way or not
Yah, coming from a C background it tripped me up at first as well. UncheckedArray[T] represents the actual array in Nim not the array pointer.
So you need ptr UncheckedArray[T] to emulate a C pointer array, e.g. an array of ints in C would become ptr UncheckedArray[int] in Nim.
I kept thinking of it as an alias for int *a but it's not. So to get an int** you'd need:
var x: ptr UncheckedArray[cstring]
f(x.addr) ## this makes a `ptr ptr array[cstring]` basically
Lately, when I have a lot of C-like indirection, I use a pua template:
template pua(T: typedesc): untyped = ptr UncheckedArray[T]
which makes pua like ptr but stays within the UncheckedArray family (meaning things like [] are already defined). It generalizes to double indirection:
let x = cast[pua pua int](0)
echo x[1][2] # Just show syntax; SEGV otherwise
Maybe not worth mentioning, but I find it slightly more ergonomic and perhaps someone else will also. Looks & sounds just different enough from "ptr" to not be confusing (like in C), but that is obviously a bit subjective.