To answer these questions it helps to understand what a string in C actually
is. `char *` simply means a pointer to a character. In C this is all that a
string is, a pointer to a character. Then it is assumed that the rest of the
string follows after the first character byte-by-byte, until a NULL byte is
encountered. So the string "Hello world" in C would actually be stored in
memory as "Hello world0" and the `char *` variable would simply be a pointer to
the "H" character.
In Nim a string is a bit more complex, it is essentially a small object that
contains the size of the string, the capacity of the allocated buffer, and the
pointer to that buffer. So the string "Hello world" would be stored as the
buffer "Hello world0000" and the object `(len: 11, cap: 14)`, note that len and
cap is without the last `0`, this is to ensure that the buffer data will always
be compatible with a `char *` in C.
So to answer your first question on how to pass a pre-allocated string to C you
are almost spot on:
# This is how you can use snprintf directly in Nim
proc snprintf(buf: cstring, cap: cint, frmt: cstring): cint {.header:
"<stdio.h>", importc: "snprintf", varargs.}
var s = newString(1024) # This allocates a buffer of NULL bytes that is
1024 characters long
echo s.len # As you can see the string is here 1024 characters long, but
printing it in Nim will not do anything because the first byte is \0 and the
string stops there.
echo cast[int](s.cstring) # cstring simply returns the pointer to the
underlying buffer, which is compatible with a C string
echo cast[int](s[0].addr) # As you can see the pointer to the first
character of the string is exactly the same as the cstring
s.setLen snprintf(s.cstring, 1024, "Foo bar %s", "Hello") # snprintf
returns the amount of bytes written into the string, we use this to update the
length of our string
echo s # Our string is now "Foo bar Hello"
echo s.len # And our length is now 13 as we would expect
Run
If we hadn't used `setLen` the string length would still return 1024, this
might not be an issue if you're only outputting it to a terminal, but if you
need the string length make sure to do this. By the way, the above sample can
be run in the Nim playground (play.nim-lang.org) to show the results.
Again on the second question you are spot on, that object will have the same
memory layout as the C struct. To convert to a `cstring` the only thing we need
to do is to get a pointer to the first character of our string, which is the
same as a pointer to the array. You can see how that works here:
proc snprintf(buf: cstring, cap: cint, frmt: cstring): cint {.header:
"<stdio.h>",
importc: "snprintf",
varargs.}
type TestObject = object
myStrData: array[1024, char]
var data: TestObject
var len = snprintf(data.myStrData.addr, 1024, "Foo bar %s", "Hello")
echo data.myStrData.addr.cstring
Run