I have a dll compiled in C with a function that has the following signature:
int wmove(WINDOW *, int, int);
In Nim I have the following corresponding procedure:
type WINDOW {.pure, final.} = object
proc wmove*(a2: ptr WINDOW; a3, a4: cint): cint {.cdecl, importc: "wmove", dynlib: "pdcurses.dll".}
When calling the procedure wmove in Nim the cint parameters seem not to be passed correctly to the C function. I inserted a printf("y=%d x=%d", a3, a4) call for the cint parameter in the C function and it prints garbage!
Does anyone know what the problem is?
OS: Windows 10 x64
Nim: Nim Compiler Version 0.17.3 [Windows: i386]
Dll: PDCurses 3.4 or 3.5, makes no difference. Compiled as described in the README using mingw-gcc x86.
Thanks
It looks like you know nothing about calling conventions (https://en.wikipedia.org/wiki/X86_calling_conventions).
You need to check what calling convention uses your DLL, and set appropriate calling convention in your function declaration.
Currently you declared cdecl, but i think (i'm not sure) that calling convention is stdcall.
stdcall is in the original pdcurses wrapper, but it produces the same result, that is why I tried cdecl.
The enire function signature is:
PDCEX int wmove(WINDOW *, int, int);
The PDCEX macro is this:
#ifdef PDC_DLL_BUILD
# ifdef CURSES_LIBRARY
# define PDCEX __declspec(dllexport) extern // <- This one is used
# else
# define PDCEX __declspec(dllimport)
# endif
#else
# define PDCEX extern
#endif
And yes, I don't know that much about type signatures, but I used cdecl with the Python3 wrapper and the SDL2 wrapper uses it, so I thought I'd try it.
Plus, another procedure:
proc wprintw*(a2: ptr WINDOW; a3: cstring): cint {.cdecl, varargs, importc: "wprintw", dynlib: "pdcurses.dll".}
works! It receives the cstring properly!
Any other ideas?
@yglukhov, THANK YOU for nudging me into trying something!
If you look at the pdcurses, it has this code:
when defined(windows):
...
const PDCURSED = "pdcurses.dll"
{.pragma: stdcall.}
else:
...
{.pragma: cdecl.}
Doesn't this set the procedure calling convention for the rest of the file? In the above example I gave, I added the cdecl in the pragma part of the procedure, but in the wrapper it's just:
proc wmove*(a2: ptr WINDOW; a3, a4: cint): cint {.importc: "wmove".}
So the abbreviated code looks like this:
{.deadCodeElim: on.}
...
when defined(windows):
...
const PDCURSED = "pdcurses.dll"
{.pragma: stdcall.}
else:
...
{.pragma: cdecl.}
# type declarations
...
{.push dynlib: PDCURSED.}
# procedure declarations
...
proc wmove*(a2: ptr WINDOW; a3, a4: cint): cint {.importc: "wmove".}
...
{.pop.}
Then I tried manually adding the noconv to every procedure declaration:
proc wmove*(a2: ptr WINDOW; a3, a4: cint): cint {.noconv, importc: "wmove".}
AND IT WORKS! Funnily, it works with either cdecl, stdcall or noconv?
Why?
Thanks for the explanation @yglukhov !
Did the pragma pragma maybe work differently two years ago when the wrapper was made? Why else would {.pragma: stdcall.} be used there?
{.push stdcall.} seems to be invalid, so I used:
when defined(windows):
...
{pragma: convention, stdcall}
else:
...
{pragma: convention, cdecl}
...
proc wmove*(a2: ptr WINDOW; a3, a4: cint): cint {.convention, importc: "wmove".}
As you can see I don't even have much experience with pragmas :), let alone calling conventions.
I've been looking at the original line {.pragma: stdcall.} in the wrapper for two days now, and always thought to myself: "I guess this pragma sets the calling convention for the entire module, the person who wrote it had to know what he was doing, right?".
As far as why all conventions seem to work, I have no idea? stdcall, cdecl and noconv seem to work, while fastcall and nimcall don't.
Thanks again @yglukhov!