0

We are implementing a number of SDK's for our suite of hardware sensors.

Having successfully got a working C API for one of our sensors, we are now starting the arduous task of testing the SDK to ensure that we haven't introduced any fatal bugs, memory leaks or race conditions.

One of our engineers has reported that when developing the test application (a Qt Widgets application), an issue has occurred when hooking onto a callback which is executed from a separate thread within the DLL.

Here is the callback prototype:

#define API_CALL __cdecl

typedef struct {
    // msg fields...
    DWORD dwSize;
    // etc...
} MSG_CONTEXT, *PMSG_CONTEXT;

typedef void (API_CALL *SYS_MSG_CALLBACK)(const PMSG_CONTEXT, LPVOID);

#define API_FUNC __declspec(dllexport)
API_FUNC void SYS_RegisterCallback(SYS_MSG_CALLBACK pHandler, LPVOID pContext);

And it is attached in Qt as follows:

static void callbackHandler(const PMSG_CONTEXT msg, LPVOID context) {
    MainWindow *wnd = (MainWindow *)context;

    // *wnd is valid 
    // Call a function here
}

MainWindow::MainWindow(QWidget *parent) {
    SYS_RegisterCallback(callbackHandler, this); 
}

My question is this: is the callback executed on the thread which creates it or on the thread which executes it? In either case, I guess it need of some kind of synchronization method. Googling has resulted in a plethora of C# examples, which isn't really whats needed.

One thing under consideration is using the SendMessage or PostMessage functions rather than going down the callback route.

Could anyone offer any suggestions please at to how cross-thread safety could be achieved using callbacks? Or is the message pump route the way to go for a Windows-based SDK?

weblar83
  • 681
  • 11
  • 32
  • "My question is this: is the callback executed on the thread which creates it or on the thread which executes it?" This would be trivial to answer on your own -- stick a breakpoint on inside the callback and see which thread you're in when your debugger hits it. – MrEricSir Aug 24 '17 at 16:08
  • @MrEricSir, apologies - I wasn't debugging the code at the time rather another engineer. But yes, this would have been trivial to discover. – weblar83 Aug 24 '17 at 17:37

2 Answers2

1

My question is this: is the callback executed on the thread which creates it or on the thread which executes it?

The thread which executes it.

Could anyone offer any suggestions please at to how cross-thread safety could be achieved using callbacks?

A simple std::queue<message> protected by a mutex is the simplest solution. Here is a good example.

Have the callback do nothing but enqueue the message, and consume it from your main thread.

  • Thank you for your suggestion. Based on the solution you provided, how would the queue get processed? Would this be done on a timer or would the implementing application need to process this queue? – weblar83 Aug 24 '17 at 17:36
  • You presumably have a main loop somewhere. Just consume messages from the queue at the start or end of each iteration of the loop. –  Aug 24 '17 at 18:07
1

Could anyone offer any suggestions please at to how cross-thread safety could be achieved using callbacks?

Yes - the standard way it's done in Qt: emit a signal from any thread. connect a slot/functor to it that executes in the context of an object living in your desired target thread. We can also do something equivalent that doesn't use signal/slots explicitly, but functions the same - after all, a slot/functor call is carried across threads in a QMetaCallEvent.

The code you show usually leads to undefined behavior since you're attempting to use the GUI object (MainWindow) from any thread other than the main thread, and it's not likely that the method you're calling is thread safe.

The proper way to do it would be to either make the MainWindow's method thread-safe, or invoke it in a thread-safe manner.

One possible approach is shown below; see this answer for for isSafe and postCall implementations.

static void callbackHandler(const PMSG_CONTEXT msg, LPVOID context) {
    auto wnd = reinterpret_cast<MainWindow*>(context);
    if (!isSafe(wnd))
      return postCall(wnd, [=]{ callbackHandler(msg, context); });

    // we're executing in wnd's thread context, any calls on wnd
    // will be thread-safe
    wnd->foo();
}

MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
    SYS_RegisterCallback(callbackHandler, this); 
}

See also this question about invoking functors across threads, and this question about invoking methods thread-safely.

Another solution would be to offer this functionality directly and have a SYS_RegisterThreadPumpCallback that would register a callback just as SYS_RegisterCallback does, but would assume that the receiving thread runs a message pump (as Qt's main thread does, and any QEventLoop-spinning thread does too). The callback would be delivered as a message to a hidden window whose wndProc then performs the actual call via a function pointer.

Yet another solution is SYS_RegisterThreadAPCCallback that would use QueueUserAPC to invoke the callback when the receiving thread is alertable. This performs somewhat better than messaging to a message pump, but can lead to trouble should the user code be alertable in unexpected places and thus unexpectedly reenter code that's not reentrant (note: reentrancy and thread safety are orthogonal concepts).

I personally would welcome an API that offered all three kinds of callbacks on Windows.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Thanks for your comment. The DLL isn't a Qt library, just a standard DLL using Windows/Linux API and the usual exported functions. Our SDK is platform agnostic so don't want to use any constructs specific to Qt in anything other than an implementing application. – weblar83 Aug 24 '17 at 17:34
  • @weblar83 I showed how to implement the application to deal with cross-thread callbacks. That's basically what your Qt users would be expected to do. You might also offer callback registration that will execute the callback in a given thread. That only works on Windows. – Kuba hasn't forgotten Monica Aug 24 '17 at 17:40
  • @weblar83 On Linux, the alertable primitives are either posix sync stuff or file descriptors. There it'd probably help to offer the user a file descriptor they could wait on, since that interfaces nicely with Qt (`QSocketNotifier`) and all other event loops (wxWindows, gtk, xlib). Posix stuff interfaces with nothing and is worthless IMHO (IIRC no app dev toolkits "mingle" posix sync primitives with their event loops). – Kuba hasn't forgotten Monica Aug 24 '17 at 17:48