Hello,
I have been experimenting with manually creating an FFI to the Linux inotify API as an exercise to learn (yes, I know this is available in the std lib). I have several questions whose answer I did not find or am unsure of. I don't want to generate code that just "happens to work".
In one experiment I simplified the Nim code as much as possible and confirmed that the following works as expected:
inotify_event* = object # An Inotify event.
wd* : FileHandle var # Watch descriptor. is cint in std/syncio.nim
mask* : uint32 # Watch mask.
cookie* : uint32 # Cookie to synchronize two events.
len* : uint32 # Length (including NULs) of nIs thsame.
name* : UncheckedArray[char] # Name.
proc inotify_init1*(flags: cint): cint {.importc .}
proc inotify_init*(): cint {.importc .}
proc inotify_add_watch*(fd: cint, pathname: cstring, mask: uint32): cint {.importc .}
proc inotify_rm_watch*(fd: cint, wd: cint): cint {.importc .}
proc read*(fd: cint, buf: pointer, count: culong): clong {.importc .}
proc close*(fd: cint): cint {.importc .}
var
EACCES {.importc, nodecl.}: cint # pretend EACCES was a variable, as
# Nim does not know its value I see most wrappers use const. Would access to global variable of a C library be the intended use-case? Are there others?
inotify_event*
{.
pure, final,
header: "<sys/inotify.h>",
importc: "struct inotify_event",
completeStruct
.} = object # An Inotify event.
wd* {.importc: "wd" .}: FileHandle # Watch descriptor. is cint in std/syncio.nim
mask* {.importc: "mask" .}: uint32 # Watch mask.
cookie* {.importc: "cookie".}: uint32 # Cookie to synchronize two events.
len* {.importc: "len" .}: uint32 # Length (including NULs) of name.
name* {.importc: "name" .}: UncheckedArray[char] # Name. I understand what completeStruct is for (documentation seems to be missing). I would like to confirm that Nim does of reordering the fields if they are not defined as in the C header. Are any other checks done?
TIA
I'm a beginner too.
Regarding your question about cdecl, I'll paraphrase the Nim documentation, but by default, a procedure has the convention nimcall (identical to fastcall). You have other choices (https://nim-lang.org/docs/manual.html#types-procedural-type) I don't think I've been much help, but I'm proud to have answered one of your questions :)
As for the rest, I'm not enough of an expert. But honestly, the documentation is really good (that's my personal opinion).
First, having used importc, why and when would one want to use cdecl?
cdecl specifies cdecl calling convention. If you don't know about calling convention: https://internet-of-tomohiro.pages.dev/nim/faq.en#procedures-what-is-a-calling-conventionqmark
Again, having used importc, why and when would one want to use nodecl?
It is rarely used. It is used when you want to import C symbols that are avaliable without #include or you define C variables or types with emit pragma and use them in Nim code.
I see most wrappers use const. Would access to global variable of a C library be the intended use-case? Are there others?
Most wrapper use const to define const variables that has the same type and value in the C library. If you want to modify global variables in C libraries, you need to use var with importc.
The most intriguing is that the code above does not use the header: "<sys/inotify.h>". Shouldn't the compilation fail? I see no include in the generated C files but all compiles and execute just fine. Apologies for my lack of knowledge regarding compiling Linux C libraries if this is obvious.
#include ... in C just inserts the content of the header file in where you write #include. If a C file declares types, consts and functions in the same way as the header file, it compiles without errors even if the header is not included.
I would like to confirm that Nim does of reordering the fields if they are not defined as in the C header.
As far as I know, Nim doesn't change the order of fields of object types. If Nim reodered fields of object types, it can cause problems when you use C libraries and reads/writes binary files.
If you don't know much about C language, my old articles about C might help: https://internet-of-tomohiro.pages.dev/nim/gccguide.en https://internet-of-tomohiro.pages.dev/nim/clibrary.en
@demotomohiro thanks for the feedback. BTW, google search already brought up your first link that I had read.
Just two notes:
Thanks once again.
importc imports a symbol from C and the calling conventions exist in C too, it does not change the calling convention from the default! It is actually mostly a name mangling option, it specifies the external name. Usually you need to use cdecl with importc but on Windows32 it might also be stdcall. That said, on most modern targets there is only one calling convention that is used. To complicate things further the .header pragma means that no symbol declaration is emitted by Nim and so it uses the right calling convention by the virtue of compiling to C...
Field reorderings are currently never performed but it is expected that the compiler will do that at some time. Use the poorly named bycopy pragma to prevent this from happening.
Field reorderings are currently never performed but it is expected that the compiler will do that
See also https://github.com/nim-lang/RFCs/issues/506 - my recommendation here would be that we actually let importc mean "use the C ABI" for a type and that would include not reordering the fields.