2017-08-24 73 views
0

我們正在爲我們的硬件傳感器套件實施許多SDK。C++ DLL - 跨線程回調

成功爲我們的傳感器獲得了一個可用的C API後,我們現在開始測試SDK的艱鉅任務,以確保我們沒有引入任何致命錯誤,內存泄漏或競爭條件。

我們的一位工程師報告說,在開發測試應用程序(Qt Widgets應用程序)時,掛鉤到從DLL內的單獨線程執行的回調時發生了問題。

這裏是回調原型:

#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); 

而且它附着在Qt的如下:

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); 
} 

我的問題是:是回調對創造或線程上執行執行它的線程?無論哪種情況,我都需要某種同步方法。谷歌搜索導致了大量的C#示例,這並不是真正需要的。

正在考慮的一件事是使用SendMessagePostMessage函數,而不是沿着回調路線。

任何人都可以提供任何建議,請問如何使用回調來實現跨線程安全?或者,消息泵路由是否適用於基於Windows的SDK?

+0

「我的問題是:是在創建它的線程還是在執行它的線程上執行的回調?」這對你自己來說是微不足道的 - 在回調函數內部保留一個斷點,並在調試器擊中它時查看你所在的線程。 – MrEricSir

+0

@MrEricSir,道歉 - 我當時沒有在調試代碼,而是另一位工程師。但是,是的,這可能是微不足道的發現。 – weblar83

回答

1

任何人都可以提供任何建議,請問如何使用回調來實現跨線程安全性?

是 - 它在Qt中完成的標準方式:從任何線程發出信號。 connect一個插槽/仿函數,它在生活在你想要的目標線程中的對象的上下文中執行。我們也可以做一些相當的事情,不明確地使用信號/時隙,但功能相同 - 畢竟,slot/functor調用是通過QMetaCallEvent中的線程進行的。

您顯示的代碼通常會導致未定義的行爲,因爲您嘗試使用除主線程之外的任何線程使用GUI對象(MainWindow),並且您調用的方法不可能是線程安全的。

正確的做法是使MainWindow的方法是線程安全的,或者以線程安全的方式調用它。

下面顯示了一種可能的方法;有關isSafepostCall的實現請參見this answer

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); 
} 

又見this question about invoking functors across threadsthis question about invoking methods thread-safely

另一種解決方案是直接提供此功能,並有一個SYS_RegisterThreadPumpCallback會註冊一個回調,就像SYS_RegisterCallback做,但會假定接收線程運行的消息泵(如Qt的主線程執行,任何QEventLoop -spinning線程也是)。該回調將作爲消息傳遞給隱藏窗口,然後通過函數指針執行實際調用。

還有另一種解決方案是SYS_RegisterThreadAPCCallback,當接收線程可提醒時,將使用QueueUserAPC來調用回調。這比向消息泵發送消息要好一些,但是如果用戶代碼在意外的地方可以提醒並且意外地重新輸入了不可重入的代碼(注意:重入和線程安全是正交的概念),可能會導致麻煩。

我個人會歡迎在Windows上提供所有三種回調的API。

+0

感謝您的評論。該DLL不是一個Qt庫,只是一個使用Windows/Linux API和常用導出函數的標準DLL。我們的SDK是平臺不可知的,因此不想在實現應用程序之外的任何其他應用程序中使用特定於Qt的構造。 – weblar83

+0

@ weblar83我演示瞭如何實現應用程序以處理跨線程回調。這基本上是你的Qt用戶應該做的。您可能還會提供將在給定線程中執行回調的回調註冊。這隻適用於Windows。 –

+0

@ weblar83在Linux上,可警示原語是posix sync stuff或文件描述符。在那裏它可能有助於爲用戶提供他們可以等待的文件描述符,因爲它與Qt('QSocketNotifier')以及所有其他事件循環(wxWindows,gtk,xlib)很好地接口。 Posix的東西沒有任何接口,是毫無價值的恕我直言(IIRC沒有應用程序開發工具包「混合」posix同步原語與他們的事件循環)。 –

1

我的問題是:在創建它的線程或執行它的線程上執行的回調?

執行它的線程。

任何人都可以提供任何建議,請問如何使用回調來實現跨線程安全?

受互斥鎖保護的簡單std::queue<message>是最簡單的解決方案。 Here就是一個很好的例子。

有回調沒有做什麼,但排隊消息,並從您的主線程消耗它。

+0

感謝您的建議。根據您提供的解決方案,隊列將如何處理?這是在一個計時器上完成,還是實施應用程序需要處理這個隊列? – weblar83

+0

你大概有一個主循環的地方。只需在循環的每次迭代開始或結束時使用隊列中的消息。 – Frank