Sharing a header file between C and C++, and using Qt-specific macros via #ifdef
-
Hi all,
For a few days now I've been struggling to understand what exactly is going wrong in my setup. I have a header file that contains code that looks like the following, in two projects which use CMake:
#if defined(Q_MOC_RUN) #include <QtQml/qqmlregistration.h> #endif /* Q_MOC_RUN */ #include <stdbool.h> typedef struct _GetIntStatusResponse_t { #if defined(Q_MOC_RUN) Q_GADGET Q_PROPERTY(bool clearToSend MEMBER getClearToSend) Q_PROPERTY(bool error MEMBER getError) Q_PROPERTY(bool rsqInterrupt MEMBER getRsqInterrupt) Q_PROPERTY(bool rdsInterrupt MEMBER getRdsInterrupt) Q_PROPERTY(bool seekTuneCompletedInterrupt MEMBER getSeekTuneCompletedInterrupt) #endif /* Q_MOC_RUN */ /* When set, the device is ready to receive the next command */ bool clearToSend; /* When set, the device has encountered an error */ bool error; /* When set, a Received Signal Quality interrupt has been triggered */ bool rsqInterrupt; /* When set, an Radio Data System interrupt has been triggered */ bool rdsInterrupt; /* When set, the Seek/Tune Complete interrupt has been triggered */ bool seekTuneCompletedInterrupt; } GetIntStatusResponse_t;The intent here is that when the C program compiles the header, it only sees the boolean members but when a Qt application does, it also invokes the meta-object complier to generate whatever it needs to in order for the properties to be accessible from a QML element.
The header itself is ferried over as a CMake configuration file together with a target file, and
target_link_librariesis used to include it to the Qt application. This is enough, and commands like#include "header.h"work without a hitch.Using a debugger I have verified that the structs arrive correctly to the C++ application side: they are read from a byte array sent over USB, and when
std::memcpyis used the debugger shows that the content of the structure instance looks correct compared to what Wireshark is showing on the wire. From this I deduce that the data moves correctly between the C program, USB and the C++ application.However, when an instance of the struct is emitted through a signal, and arrives to the QML side it is completely empty i.e. using
console.logtogether withJSON.stringifyI get the following the application's console:qml: Received status report: QVariant(_GetIntStatusResponse_t, ) qml: Stringified: ""Naturally, if I try to access any of the members I get
undefined. The data itself has mysteriously vanished somewhere during the conversion from a C++ instance to a QVariant-wrapped object.I can provide more code and details if necessary but does anyone have any ideas on what could be going wrong? I have tried stepping through the code that comes when I call
emitbut it goes so deep into how Qt works that I just can't make sense of it.I have already tried the following:
- Using
#ifdef __cplusplusinstead; this leads to a number of linker errors related to a function calledstaticMetaObject, one per struct type - Omitting Qt stuff from the structs in the shared header, and instead defining more struct in the C++ side; this works but seems rather hacky. There must be a way to coax MOC to do my bidding.
- Including the shared headers inside
qt_add_executable; this has no effect, the properties are stillundefined.
- Using
-
Hi,
Do I understand correctly that you are trying to load a binary representation of one struct in C into what is technically a different struct in C++ ?
memcpyis the wrong tool here, you should initialize your C++ struct based on the C one. These two have different memory layouts thus overwriting the memory taken by the latter with the former is a recipe for tragedy. -
Hi,
Do I understand correctly that you are trying to load a binary representation of one struct in C into what is technically a different struct in C++ ?
memcpyis the wrong tool here, you should initialize your C++ struct based on the C one. These two have different memory layouts thus overwriting the memory taken by the latter with the former is a recipe for tragedy.@SGaist What I'm doing is creating an instance of a "report struct" that uses a union type in the C application, filling the proper portion of that, send it as a HID report, and then parse the bytes on the other side.
Here is the glue which makes it work:
typedef struct _Report_t { /* Identifier of the report */ ReportIdentifier_t identifier; /* Report bytes */ union ReportBytes { GetIntStatusResponse_t interruptStatus; ... other structs ... //* This defines the "maximum size" the report can have; one byte is reserved for the identifier */ uint8_t raw[MAX_REPORT_SIZE - 1]; } bytes; } Report_t;The relevant portion of the C++ code looks like this. It uses the
hidapilibrary:unsigned char buf[MAX_REPORT_SIZE]; int res = hid_read_timeout(m_source, buf, sizeof(buf), 250); if (res > 0) { ReportIdentifier_t identifier = (ReportIdentifier_t)buf[0]; switch (identifier) { ... other report types ... case REPORT_IDENTIFIER_INTERRUPT_STATUS: { GetIntStatusResponse_t report; std::memcpy(&report, &buf[1], sizeof(GetIntStatusResponse_t)); emit intStatusReportReceived(report); break; } }If I look at the report object in debugger, it is correct each and every time. The problems surface once the struct is pushed towards QML with
emit. -
I managed to stumble across a solution. My problem was that the header files provided by the first CMake project have to be explicitly added to the
qt_add_executablestanza so that the meta-object compiler picks them up, and when I first tried it the variable I thought I read them to was, in fact, empty.This is why I received so many linker errors when using the
ifdef __cplusplusin the header file. The shared header file was being used through an#importstatement but for some reason meta-object compiler only processed the header file which included it, not the shared header itself.After noticing that the variable was empty I explicitly added the header files to
qt_add_executable, and much to my surprise the linker error is now gone, and everything works. -
A AnttiK has marked this topic as solved