I have successfully interfaced Nim with Tcl/Tk, or to be more precise, Pixie with Tcl/Tk 8.6.
A few months ago, Tcl/Tk9 was released and I'm trying to modify my library accordingly.
I'm facing a problem because some functions in Tcl/Tk9 have been modified and I can't find one of them.
Below is the C function I want to interface with Nim :
https://github.com/tcltk/tcl/blob/f152ab6f3f207fe3491290cd83359dc399e188a0/generic/tcl.h#L2314-L2315
const char * Tcl_InitStubs(Tcl_Interp *interp, const char *version, int exact, int magic);
And here's how I wrote it in Nim :
type
PInterp* = ptr TInterp
TInterp*{.final.} = object
proc tclInitStubs(interp: PInterp, version: cstring, exact: cint, magic: cint): cstring {.cdecl, importc: "Tcl_InitStubs".}
What I'm not so good at is declaring my magic value in Nim.
https://github.com/tcltk/tcl/blob/f152ab6f3f207fe3491290cd83359dc399e188a0/generic/tcl.h#L2302
# First solution
const MAGIC* = int(0xFCA3BACB) + int(sizeof(pointer))
# Second solution
let MAGIC*{.importc: "TCL_STUB_MAGIC", header: "tcl.h".} : cint
First solution , this error :Error: type mismatch
I'm not surprised, magic should be cint and not int like MAGIC const
Second solution , this error :
error: macro "Tcl_InitStubs" passed 4 arguments, but takes just 3 : N_CDECL(NCSTRING, Tcl_InitStubs)(tyObject_TInterp__Jswd8GUVEadjMnvFYXdisQ* interp_p0, NCSTRING version_p1, int exact_p2, int magic_p3);
error: 'Tcl_InitStubs' redeclared as different kind of symbol : N_CDECL(NCSTRING, Tcl_InitStubs)(tyObject_TInterp__Jswd8GUVEadjMnvFYXdisQ* interp_p0, NCSTRING version_p1, int exact_p2, int magic_p3);
Now I'm more worried! Since I'm a Nim beginner and don't know C, I hope I'll be able to do without the 2nd solution.
I'd like to use the 1st solution but I don't know how to cast my const into a cint. if that's possible ?
Your link shows that Tcl_InitStubs is actually redefined as a C macro:
#ifdef USE_TCL_STUBS
#if TCL_MAJOR_VERSION < 9
# if TCL_UTF_MAX < 4
# define Tcl_InitStubs(interp, version, exact) \
(Tcl_InitStubs)(interp, version, \
(exact)|(TCL_MAJOR_VERSION<<8)|(0xFF<<16), \
TCL_STUB_MAGIC)
[...]
A benefit of Nim being compiled to C is that it can deal with such macros, by declaring the procedure as .nodecl:
proc tclInitStubs(interp: PInterp, version: cstring, exact: cint): cstring {.nodecl, importc: "Tcl_InitStubs", header: "tcl.h".}
Since the macro needs no magic parameter, it should be enough to solve the problem.
(It is also possible to make MAGIC a cint - albeit lossily...
const MAGIC* = cast[cint](0xFCA3BACB) + cint(sizeof(pointer))
but even then, you'd still have to replicate what the C macro does in Nim (it also bit-ors in other constants), so I recommend sticking with .nodecl.)
C macros in C header files can be imported without nodecl pragma.
For example:
testcheader.h:
#pragma once
#define SOME_C_MACRO(x) cfuncImpl(x, 777)
#define SOME_C_MAGIC 1234
#include <stdio.h>
static inline void cfuncImpl(int x, int y) {
printf("x = %d, y = %d\n", x, y);
}
testcheader.nim:
proc someCMacro(x: cint) {.importc: "SOME_C_MACRO", header: "testcheader.h".}
someCMacro(1001)
let someCMagic {.importc: "SOME_C_MAGIC", header: "testcheader.h".}: cint
someCMacro(someCMagic)
Output:
$ nim c -r testcheader.nim
x = 1001, y = 777
x = 1234, y = 777
Ah, right, thanks. It even says so in the manual, no idea why I thought otherwise...
I guess, then, this should just work:
proc tclInitStubs(interp: PInterp, version: cstring, exact: cint): cstring {.importc: "Tcl_InitStubs", header: "tcl.h".}
It is also possible to make MAGIC a cint - albeit lossily
I'd rather go this way, Can you tell me why you wrote this? What is the potential danger ?
The magic cast itself should work without issues; the difference is between programming for the API or ABI. By setting .header (as @demotomohiro points out, no .nodecl needed), you are importing the C API, which is documented by Tcl: https://www.tcl.tk/man/tcl/TclLib/InitStubs.htm
If you do it without .header, then you are programming against the ABI, which (from what I can tell) is undocumented, and may change more often. Also, the C compiler doesn't see the header, so it won't complain if you imported something incorrectly.
In practice, both can work, but the API also automatically figures out the magic number for you, calls the correct function per Tcl version, etc. It seems tedious to re-implement all this manually.