I am calling nim from C. I want to pass an array of integers, and I definitely want to avoid copying the entire array.
I've tried c2nim for ideas, but it produces cint and cstringArray, which I do not see defined anywhere. I've tried ptr int8, but I have trouble incrementing the pointer.
What is the recommended signature of the Nim proc? And how do I index the array in Nim?
type
mypair = tuple[a,b: cint]
arr16 = array[0 .. 15, cint]
proc myptrtest(p: pointer, indx: cint) : mypair =
let iptr1 = cast[int](p); let ptr2 = cast[ptr array[0 .. 0, cint]](p)
let ptr1 = cast[ptr cint](iptr1 +% indx *% sizeof(cint))
result = (ptr1[], ptr2[indx])
var myarray: arr16
echo sizeof(int)," ", sizeof(cint) # on WIN64, "cint" has 32 bits....
myarray[7] = 1234
echo myptrtest(cast[pointer](addr myarray),7)
# should respond with: (a: 1234, b: 1234)
# calling type sig in C: myptrtest(int *p, int indx) (with sizeof(int) == 4)
# return type requires an appropriate struct.
A type "pointer" in Nim seems to be a void* pointer in C. Pointer arithmetic in Nim can be done with cast to "ByteAddress" - and "ByteAddress" is declared as int in system.nim. In the example, ptr1 is directly dereferenced to a cint, and ptr2 performs array access via [indx].
Length is not fixed.
Unchecked array seems to be working:
type
CArray{.unchecked.}[T] = array[0..0, T]
proc func*(b: CArray[char], size: int32, e: ptr ptr char, val: ptr cint): cint
{.cdecl, exportc, dynlib.} =
echo(b[size-1])
return 0
From C:
char* b;
char* e;
int val;
int rv = func(b, (e - b), &e, &val);
I'm not sure when to use cint vs. int32, but the code works. c2nim tries to use cstring, but these are not null-terminated arrays.
pointer and ByteAddress are definitely interesting for simple casting and pointer arithmetic.
(I modified my post to provide the size parameter.)
Is there a way to cast either a ptr char or a cstring to something which allows me to assign to an indexed character? I cannot cast to the unchecked CArray.
The following template allows you to do C-like pointer arithmethic.
template ptrMath*(body: untyped) =
template `+`*[T](p: ptr T, off: int): ptr T =
cast[ptr type(p[])](cast[ByteAddress](p) +% off * sizeof(p[]))
template `+=`*[T](p: ptr T, off: int) =
p = p + off
template `-`*[T](p: ptr T, off: int): ptr T =
cast[ptr type(p[])](cast[ByteAddress](p) -% off * sizeof(p[]))
template `-=`*[T](p: ptr T, off: int) =
p = p - off
template `[]`*[T](p: ptr T, off: int): T =
(p + off)[]
template `[]=`*[T](p: ptr T, off: int, val: T) =
(p + off)[] = val
body
when isMainModule:
ptrMath:
var a: array[0..3, int]
for i in a.low..a.high:
a[i] += i
var p = addr(a[0])
p += 1
p[0] -= 2
echo p[0], " ", p[1]
Note that you need to do it inside a ptrMath section so that the (generally unsafe) pointer arithmetic doesn't spill over into the rest of the code.
Edit: Fixed code to properly use unsigned arithmetic.
# But why this:
cast[ptr type(p[])](cast[ByteAddress](p) + off * sizeof(p[]))
# instead of this:
cast[ptr T](cast[ByteAddress](p) + off * sizeof(T))
# ?
cdunn2001: Would type(p[]) or sizeof(p[]) be runtime calculations?
No.
This no longer compiles.
foo.nim(155, 7) template/generic instantiation from here
foo.nim(66, 15) Error: 'export' is only allowed at top level
template `+`*[T](p: ptr T, off: int): ptr T =
^
I think the start (*) needs to be removed. Also, is there a way to suppress warnings for these specific templated procs?
{.push hint[XDeclaredButNotUsed]: off.}
template ...
{.pop.}
That did not work.cdunn2001: This no longer compiles.
Nothing changed, it was actually always a bug. Yes, the inner export markers need to go, but they should never have been there in the first place. It works in the test only because there the code is not inside a procedure.
cdunn2001: Also, is there a way to suppress warnings for these specific templated procs? ... That did not work.
This works if you put them directly in your code, but not if you put them in the template. I think that's because pragmas are handled while the template is being parsed.
Very useful templates for anyone interfacing with C. Thank you very much.
Since I usually do a lot of pointer work in a given file, I switched to this:
template usePtr*[T]() =
template `+`(p: ptr T, off: Natural): ptr T =
cast[ptr type(p[])](cast[ByteAddress](p) +% int(off) * sizeof(p[]))
template `+=`(p: ptr T, off: Natural) =
p = p + off
template `-`(p: ptr T, off: Natural): ptr T =
cast[ptr type(p[])](cast[ByteAddress](p) -% int(off) * sizeof(p[]))
template `-=`(p: ptr T, off: Natural) =
p = p - int(off)
template `[]`(p: ptr T, off: Natural): T =
(p + int(off))[]
template `[]=`(p: ptr T, off: Natural, val: T) =
(p + off)[] = val
...
usePtr[my_type]()
Note the use of Natural. C coders often use unsigned for bit manipulations or array indices, and c2nim converts those to cuint, which do not work for array look-ups.
Actually, I wonder why uint is not used for Nim bit manipulators. "Signed" operations are confusing. Do you know the reason? (ByteAddress is int now, btw. https://nim-lang.org/docs/system.html#ByteAddress )
You should be able to use SomeInteger instead, which is declared as:
type
SomeSignedInt* = int|int8|int16|int32|int64
SomeUnsignedInt* = uint|uint8|uint16|uint32|uint64
SomeInteger* = SomeSignedInt|SomeUnsignedInt
Convert the argument to int via int(off), and you should be good.