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

Reply via email to