There is a libcec library, which provides usefull info i need only in form of callbacks.
Bindings show callback form needed as:
typedef int (*CBCecLogMessageType)(void*, const cec_log_message);
So, cec_log_message looks like this:
typedef struct cec_log_message
{
char message[1024]; /**< the actual message */
cec_log_level level; /**< log level of the message */
int64_t time; /**< the timestamp of this message */
} cec_log_message;
Which translated to this in Nim:
cec_log_message* = object
message*: array[1024, char] #*< the actual message
level*: cec_log_level #*< log level of the message
time*: int64_t #*< the timestamp of this message
Procedures in Nim:
type
CBCecLogMessageType* = proc (a1: pointer; a2: cec_log_message): cint
proc monitorCecTraffic(a1: pointer, msg: cec_log_message): cint =
...
And generated C file for this project contains this declaration:
N_NIMCALL(int, monitorcectraffic_153050)(void* a1, ceclogmessage150226* msg);
Which is obviously puts trash on stdout if called like echo msg.level. Now what really needed is (notice msg is not a poiner anymore):
N_NIMCALL(int, monitorcectraffic_153050)(void* a1, ceclogmessage150226 msg);
And that is achievable by bycopy pragma on cec_log_message type:
cec_log_message* {.bycopy.} = object
message*: array[1024, char] #*< the actual message
level*: cec_log_level #*< log level of the message
time*: int64_t #*< the timestamp of this message
With bycopy all works as expected.
So, problem is: it looks and feels too hacky. What if other procedures require not by copy passing, but by ref passing? Seems wrong to solve problem with type when it lies in fuction declaration.
My question: is it the only right way to do this? Can functions exported to C be more finely tuned?
Personnaly I would expect something like:
# warning, not a valid Nim
proc monitorCecTraffic(a1: pointer, msg: cec_log_message {.bycopy.}): cint =
...
cdecl and exportc does not seem to affect the problem in any way.
Here you go: https://github.com/d3m1gd/nim-to-c-callbacks
Points to note:
One needs CEC adapter and libcec to fully reproduce. Otherwise the code is simplified and is plain diff would be sufficient to see the changes between working and broken versions (main change is in the cectypes/cectypesbroken files).
Also not mentioned before, there is a bug with threading. Also presented in repository (look at makefile). Bug is introduced simply with compiling fullg operational file with threads:on, with nothing else changed. Threads are not used in Nim code. Related backtrace:
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x763847b0 (LWP 1434)]
0x0000a024 in lowgauge_33018 ()
(gdb) bt
#0 0x0000a024 in lowgauge_33018 ()
#1 0x0000a200 in prepareforinteriorpointerchecking_47082 ()
#2 0x000147d4 in collectctbody_76419 ()
#3 0x00014ce4 in collectct_53810 ()
#4 0x00015794 in rawnewobj_59604 ()
#5 0x00015928 in newObj ()
#6 0x000159c4 in rawNewString ()
#7 0x00016cf4 in toNimStr ()
#8 0x00016d70 in cstrToNimstr ()
#9 0x0002d4c4 in monitorcectraffic_98004 ()
#10 0x76f7b8e4 in CEC::CCECClient::CallbackAddLog(CEC::cec_log_message const&) () from /usr/lib/libcec.so.2
#11 0x76f7be80 in CEC::CCECClient::AddLog(CEC::cec_log_message const&) () from /usr/lib/libcec.so.2
#12 0x76f6fbb4 in CEC::CLibCEC::AddLog(CEC::cec_log_level, char const*, ...) () from /usr/lib/libcec.so.2
#13 0x76f623fc in CEC::CCECProcessor::Process() () from /usr/lib/libcec.so.2
#14 0x76f67354 in PLATFORM::CThread::ThreadHandler(void*) () from /usr/lib/libcec.so.2
#15 0x76effda4 in start_thread () from /usr/lib/libpthread.so.0
#16 0x76e90dc0 in ?? () from /usr/lib/libc.so.6
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
Would be glad to answer any questions.
libcec seems to invoke your callback from another thread. You have to call system.setupForeignThreadGc in your callback and even then it's fishy, I can only guess that it happens to "work" with --threads:off.
Regarding the bycopy issue. Please make a feature request for that. That said, passing 1 K buffer by value on the stack looks like a libcec bug to me.
That is correct, it works with --threads:off. Will try to refactor my app for single thread then, no problem about that.
That said, passing 1 K buffer by value on the stack looks like a libcec bug to me.
It seems like one, because they use call by ref in C++ procedures, but leave only call by copy for C api:
// C++ function example
void CCECClient::CallbackAddLog(const cec_log_message &message)
Thanks for the hint about system.setupForeignThreadGc, will look into it.
Made some testing, and system.setupForeignThreadGc it fact does work and no segfault happens.
Can you elaborate a bit on why is it fishy? Should i use such a solution?
Also, a semi-related question: in a git repo i set up can be seet a file stdint.nim, which is a hand written unportable hack to get some types from <stdint.h>. Now, is there a proper <stdint.h> interface out there? I had bad luck searching for it.
Can you elaborate a bit on why is it fishy?
It's fishy because you run Nim code in a different thread but the compiler is not aware of this and thus cannot protect you. However, the fix is very simple: Declare the callback as gcsafe and then the compiler tells you whether it's safe.
How hard would it be to wrap it though?
Well, a bit hard for me to do whole wrap. c2nim cant fully process the header, being confused by __extension__ and
/* Error: Uncaught parsing exception raised */
# define __INT64_C(c) c ## L
/* Error: number 18446744073709551615 out of valid range */
# define UINT64_MAX (__UINT64_C(18446744073709551615))
So i exactly ended up wrapping only couple of types needed. Just war wondering if it is the best i can do. Thanks for you answers, that's one nice community you have here ;)