Dear community,
first, thanks for the great docs, issue discussions and forum replies – those express a friendly atmosphere and are great resources to learn. I am new to Nim and try to achieve something that is apparently above my level of experience – please excuse any fuzziness in my questions. :)
I created bindings for a C plug-in specification, so plug-ins (i.e., shared objects) can be written using Nim (e.g.). Now, I want to create a "convenience layer" which hides the FFI details (ctypes, required pragmas, …) for implementers of such plug-ins.
Here is an example (it might contain errors, but it hopefully can convey the idea):
# file "iface-ext.nim" – this is the interface which must be
# implemented to comply with the plug-in specification
type
cuint32* {.importc: "uint32_t", header: "<stdint.h>".} = uint32
aContainer* {.pure.} = object
aNumber* = cuint32
aDescriptor* {.pure.} = object
aProc*: proc (container: aContainer): aContainer {.cdecl.}
anInitializer* = proc (index: cuint32): aDescriptor
{.cdecl, exportc, dynlib, extern: "create".}
# file "implementation.nim" – this is how programmers should be able
# to implement the interface: without the details regarding FFI, e.g.,
# using Nim types only, with variables retrieved from containers, etc.
import "iface-int"
proc aProc(number: uint): uint =
number + 1
proc anInitializer(index: uint): aDescriptor =
result = createShared(aDescriptor)
result.aProc = aProc
# file "iface-int.nim"
import "iface-ext"
# What to write here to make "implementation.nim" compatible to
# "iface-ext.nim"?
# How to allow assignment of implementation with type
# ``proc (number: uint): uint`` to ``aDescriptor.aProc`` of type
# ``proc (container: aContainer): aContainer``? Via a converter which
# extracts the integer from the container prior invocation and puts it
# back after? Via a decorator around the implementation of ``aProc``?
# How to add ``{.cdecl, exportc, dynlib, extern: "create".}`` to
# ``anInitializer``? Via template?
Thanks in advance.
I am not if I understand properly the question. Besides I am a newbie myself. Nonetheless I did a similar project (I should give it a little bit more love). You can find it here: fmi.nim.
If I remember well, I created templates with the same purpose that you are looking for.
The code is crappy (I didn't clean the experiments), but I managed to create a working example. If you look for exportc and for template in the repository, you might get some inspiration. I asked many question about this project in this forum. That might help you as well.
Good luck!
I believe you just have to convert the types to their equivalents.
proc doSomething(num: int) =
callBindingCode(num.cint)
if there are casting, buffers, ptrs etc involved in the C code, you can try to hide them in a nice api.
the body of your proc will do the ugly stuff, and the programmer only sees the elegant API you created.
# How to allow assignment of implementation with type > # proc (number: uint): uint to aDescriptor.aProc of type > # proc (container: aContainer): aContainer?
Via a converter which > # extracts the integer from the container prior invocation and puts it > # back after?
Converters would be a bit overkill and likely more confusing. Probably it'd be easiest to stick to getting users to create the correct proc type directly. You don't need ctypes quite the same as in say Python, though see below.
If needed provide a proc or a template that does a simple conversion that users could use like @kobi mentioned.
# How to add {.cdecl, exportc, dynlib, extern: "create".} to > # anInitializer? Via template?
That's a bit trickier. A quick and dirty solution could be to create a block template that creates a proc with the correct annotations and a custom name. Like:
template extApiFunction(name: untyped, body: untyped) =
proc `name Ext` {.cdecl, exportc, dynlib, extern: "create".} =
echo "hey, do some setup..."
body # this is the user's code
extApiFunction(doSomething):
echo "useful code"
This would create a proc "doSomethingExt" that'd be the correct proc type. Later on you could create custom pragma that'd apply the template, but I've never made one. Though Nimler might be a good example for a plugin api.
A couple of hopefully helpful notes on C types:
cuint32* {.importc: "uint32_t", header: "<stdint.h>".} = uint32
That shouldn't be needed. Nim's uint32 matches C's uint32_t. The distinction for cint vs int can be different depending on 32/64 bit or different OS'es, so cint is needed to match a standard int in C.
I can't tell from your example if aContainer is an external C object. If it is then you likely want to import it from C and import the appropriate headers with it. Something like: