Funkcja MainThread
nie jest funkcją składową (metodą) klasy MainWindow
. Tym samym nie ma dostępnego this
w tej funkcji (niejawnie również).
Możesz przekazać this
jako parametr, który system wołając MainThread
przekaże jako pierwszy argument.
Z MSDN:
HANDLE CreateThread(
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress,
[in, optional] __drv_aliasesMem LPVOID lpParameter,
[in] DWORD dwCreationFlags,
[out, optional] LPDWORD lpThreadId
);
[in, optional] lpParameter
A pointer to a variable to be passed to the thread.
Więc robisz tak:
mainth = CreateThread(NULL, 0, MainThread, NULL, 0, NULL);
zmieniasz na:
mainth = CreateThread(NULL, 0, MainThread, this, 0, NULL);
Wtedy w tej funkcji:
DWORD WINAPI MainThread(LPVOID lpParam)
lpParam
jest wskaźnikiem na MainWindow
. Musisz zadbać aby obiekt przekazany do wątku był dostępny przez cały czas życia wątku (albo przynajmniej w momencie kiedy operujemy na obiekcie). Całkiem łatwo możesz się upewnić, że obiekt ciągle istnieje bo to MainWindow
zarządza wątkiem. Więc wywołanie ExitThread
w destruktorze MainWindow
powinno zrobić robotę (+ może WaitForSingleObject
).
Teraz pozostaje jedynie zrzutować wskaźnik, i masz dostęp do metod klasy:
DWORD WINAPI MainThread(LPVOID lpParam)
{
MainWindow *const mainWindow = static_cast<MainWindow *>(lpParam);
while(!abortth) {
if (!pauseth) {
nlohmann::json data = mprog.readjsondata();
if (data["command"] == "ping") {
mprog.sendText("{\"command\":\"pong\"}");
}
if (data["command"] == "newGlobalChatMessage") {
mainWindow->appendMessage(QString::fromStdString(data["author"].dump()),QString::fromStdString(data["value"].dump()));
}
}
}
return 0;
}
Problemem będzie to, że twój program nie będzie działał dobrze. Metoda appendMessage
odwołuje się do pamięci, która jest również używana w głównym wątku aplikacji. Przez to wysoce prawdopodobne jest to, że twój program będzie się sypał. Równoległe pisanie do pamięci i odczyt z pamięci (tej samej) z dwóch różnych wątków spowoduje crash całego programu.
Na przykład tutaj:
QScrollBar *bar = ui->textEdit->verticalScrollBar();
bar->setValue(bar->maximum());
jest modyfikowana pamięć - wartość w klasie QScrollBar. Wysoce prawdopodobne jest, że główny wątek rysujący okno aplikacji będzie czytało tą samą wartość z tego samego obiektu UI. QScrollBar::setValue(...)
i QScrollBar::value()
nie mają mechanizmu synchronizacji między wątkami (to wołanie nie jest thread-safe).
Żeby to naprawić musisz jakoś zsynchronizować między sobą dwa wątki. Tutaj myślę najlepszym i najprostszym rozwiązaniem pewnie będzie kolejka wiadomości.
Tutaj jest na pewno kilka możliwości: bazujące na tym co już jest w Qt albo bazujące na własnym kodzie.
Bazując na Qt:
Można stworzyć własną klasę eventu, którą wyślemy do MainWindow
i obsłużymy tam:
class MyMessageEvent : public QEvent
{
public:
static const Type s_type = static_cast< Type >( QEvent::User + 1 );
explicit MyMessageEvent( QString from, QString message ) : QEvent( s_type ), m_from( from ), m_message( message ) {}
const QString &GetFrom() const { returm m_from; }
const QString &GetMessage() const { return m_message; }
private:
QString m_from;
QString m_message;
};
// gdzieś w .cpp
const QEvent::Type MyMessageEvent::s_type;
Czytając z:
https://doc.qt.io/qt-5/threads-qobject.html
You can manually post events to any object in any thread at any time using the thread-safe function QCoreApplication::postEvent(). The events will automatically be dispatched by the event loop of the thread where the object was created.
Więc to:
if (data["command"] == "newGlobalChatMessage") {
appendMessage(QString::fromStdString(data["author"].dump()),QString::fromStdString(data["value"].dump()));
}
zamieniamy na:
if (data["command"] == "newGlobalChatMessage") {
QApplication::postEvent(mainWindow, new MyMessageEvent(QString::fromStdString(data["author"].dump()),QString::fromStdString(data["value"].dump())));
}
A w MainWindow
musimy obsłużyć event:
class MainWindow /* .... coś tutaj jest */
{
/* .... coś tutaj jest */
virtual bool event( QEvent *event ) override
/* .... coś tutaj jest */
};
bool MainWindow::event( QEvent *event )
{
if( event->type() == MyMessageEvent::s_type )
{
MyMessageEvent *const typedEvent = static_cast< MyMessageEvent * >( event );
appendMessage( typedEvent->GetFrom(), typedEvent->GetMessage() );
return true;
}
else return <<<klasa bazowa>>>::event( event );
}
Druga opcja to do synchronizacji wątków możesz użyć klasy z Qt: QReadWriteLock
(ewentualnie QMutex
, albo z STL: std::mutex
).
Jednocześnie w głównym wątku aplikacji musisz jakoś skonsumować kolejkę wiadomości i dodać ją do UI.
Nie będę pokazywał, poszukać musisz już sam.
Dużym problemem również jest to, że korzystasz z frameworku, który oferuje już wielowątkowość i mieszasz do tego API systemowe. Chcąc przeportować swoją aplikację na np. Linux, będziesz musiał napisać oddzielną implementację na Linuxa. Poza tym, korzystając z API do multithreadingu z Qt pewnie będziesz miał dostępne od ręki prostsze mechanizmy do synchronizacji.