I'm using a library that takes a const char** as a buffer that it fills in. The wrapper for the library takes a cstringArray. So, given that I know the number and max length of the strings that will be returned, what is the best way to allocate (and deallocate!) a cstringArray to pass in?
Thanks!
The system module defines:
proc allocCStringArray*(a: openArray[string]): cstringArray
While you could create a "fake" a param with all the right string lengths & just call that, it would be more efficient to instead adapt the like 6 lines of code impl in system.nim to your needs, i.e. taking (n, maxLen: int).Ok, thanks. I tried using the seq -> cstringArray conversion before ('faking' it, as you say), and also now tweaking the allocCString proc for my use case, but in both cases, once I pass the cstringArray into my wrapped c proc and it gets written to, the dealloc fails. And, just writing to the cstringArray outside the proc, causes the same problem
Here's a minimum example of what I've tried:
var testSeq:seq[string]
testSeq.add("test")
var testCSA = allocCStringArray(testSeq)
testCSA[0] = "test"
deallocCStringArray(testCSA)
this gives the error: "virtualFree failing!Error: execution of an external program failed"
Based on what you wrote, I tried this as well:
proc newCStringArray*(len, stringSize: int): cstringArray =
## Creates a NULL terminated cstringArray from `a`. The result has to
## be freed with `deallocCStringArray` after it's not needed anymore.
result = cast[cstringArray](alloc0((len+1) * sizeof(cstring)))
for i in 0 ..< len:
result[i] = cast[cstring](alloc0(stringSize+1))
var testCSA = newCStringArray(2, 256)
testCSA[0] = "test"
deallocCStringArray(testCSA)
And it gives the same error ("virtualFree failing!Error: execution of an external program failed").
For reference, here is the deallocator:
proc deallocCStringArray*(a: cstringArray) =
## Frees a NULL terminated cstringArray.
var i = 0
while a[i] != nil:
dealloc(a[i])
inc(i)
dealloc(a)
This is all with Nim version 1.4.2, and compiling with nim c -r main.
testCSA[0] = "test" cannot work because you assign a string literal which was not alloc'ed so deallocCStringArray crashes. You need to use something like:
# WARNING; untested code
proc storeLiteral(dest: var cstring; src: cstring) =
if dest != nil: dealloc(dest)
let s = src.len+1
dest = alloc(s)
copyMem(addr dest[0], unsafeAddr src[0], s)
var testCSA = newCStringArray(2, 256)
storeLiteral testCSA[0], "test"
deallocCStringArray(testCSA)
In C you can call strdup but then you need to use C's free, not dealloc... It's complicated.
I am just assigning the string as a test, so the fact that it fails isn't important to me specifically - I was just trying to get a minimal example, but I see that it wasn't a good one, sorry. Thanks for the explanation of why. I tested your snippet anyway and it just needed a cast to cstring for the alloc to dest, but otherwise works perfectly.
The function I am passing the cstringArray into is as follows:
#extern DECLSPEC SDL_bool SDLCALL SDL_Vulkan_GetInstanceExtensions(
# SDL_Window *window,
# unsigned int *pCount,
# const char **pNames);
proc vulkanGetInstanceExtensions*(window: WindowPtr, pCount: ptr cuint, pNames: cstringArray): Bool32 {.
importc: "SDL_Vulkan_GetInstanceExtensions".}
So it needs to write into the buffer I pass. But if I use the following:
proc newCStringArray*(len, stringSize: int): cstringArray =
## Creates a NULL terminated cstringArray from `a`. The result has to
## be freed with `deallocCStringArray` after it's not needed anymore.
result = cast[cstringArray](alloc0((len+1) * sizeof(cstring)))
for i in 0 ..< len:
result[i] = cast[cstring](alloc0(stringSize+1))
proc main =
# init stuff
var requiredExtensionsCS = newCStringArray(extensionCount.int, VK_MAX_EXTENSION_NAME_SIZE)
discard vulkanGetInstanceExtensions(window, extensionCount.addr, requiredExtensionsCS)
deallocCStringArray(requiredExtensionsCS)
it gives a SIGSEGV error:
C:\Nim\nim-1.4.2\lib\system\alloc.nim(972) dealloc
C:\Nim\nim-1.4.2\lib\system\alloc.nim(871) rawDealloc
C:\Nim\nim-1.4.2\lib\system\avltree.nim(74) del
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
Try this:
proc newCStringArray*(len: int): cstringArray =
result = cast[cstringArray](alloc0((len+1) * sizeof(cstring)))
proc main =
# init stuff
var requiredExtensionsCS = newCStringArray(extensionCount.int)
discard vulkanGetInstanceExtensions(window, extensionCount.addr, requiredExtensionsCS)
dealloc(requiredExtensionsCS)