wxWidgets - wątki i eventy - komunikat Acces Violation

0

Witam,

Wydawało mi się, że juz mniej wiecej rozumiem, jak działa mechanizm wątków i eventów w wxWidgets, ale jak widać nie do końca.
Celem moim było napisanie najprostszej aplikacji korzystającej z jednego wątku. Wątek powinien informować o postępie okno aplikacji poprzez mechanizm eventów.

Najpierw więc utworzyłem najprostszą klasę wątku:

MyThread.h

#pragma once
#include <wx/wx.h>
#include <wx/thread.h>
#include <wx/url.h>
#include <wx/file.h>
#include "MyEvent.h"



class MyThread : public wxThread
{
private:
	wxWindow *m_pParent;			
	

public:
	MyThread(wxWindow *pParent);	
	virtual ~MyThread(void);		
	
	
	virtual void *Entry();

	
	virtual void OnExit();

};

MyThread.cpp

#include "MyThread.h"


///---------------------------------------------------------------------------------------------
MyThread::MyThread(wxWindow *pParent): wxThread(wxTHREAD_DETACHED), m_pParent(pParent)
{
 
	if ( this->Create() != wxTHREAD_NO_ERROR )
	{
		wxLogError(wxT("Nie można utworzyć wątku!"));
	}
	else
	{
		if ( this->Run() != wxTHREAD_NO_ERROR )
		{
			wxLogError(wxT("Nie można uruchomić wątku!"));
		}
	}
	
}

///---------------------------------------------------------------------------------------------
MyThread::~MyThread(void)
{
}
///---------------------------------------------------------------------------------------------
void *MyThread::Entry(void)
{
	MyEvent event( wxEVT_TEST,GetId() );
	event.SetEventObject( (wxObject *)this->This() );
	int p=0;
	
	/*Operacje wykonywane przez wątek*/
    while(1)
	{

		event.SetProgress(p++);
		wxSleep(1);
		m_pParent->GetEventHandler()->AddPendingEvent( event );

	}

	return 0;
}

///---------------------------------------------------------------------------------------------
void MyThread::OnExit(void)
{
	/*Operacje po zakończeniu wątku*/
}
///---------------------------------------------------------------------------------------------

... a następnie klasę do obsługi eventów:

MyEvent.h

#pragma once
#include <wx/event.h>
#include "MyThread.h"

class MyThread;

class MyEvent: public wxNotifyEvent
{
public:
	MyEvent(wxEventType commandType = wxEVT_NULL, int id = 0 );
    MyEvent(const MyEvent& event);

	virtual ~MyEvent(void);


	virtual wxEvent *Clone() const
		{ return new MyEvent(*this); }

private:
	DECLARE_DYNAMIC_CLASS(MyEvent);
	int m_progress;

    
	void SetProgress(int);
	int GetProgress(void);
	


};

typedef void (wxEvtHandler::*MyEventFunction)(MyEvent&);

BEGIN_DECLARE_EVENT_TYPES()
    DECLARE_EVENT_TYPE(wxEVT_TEST, wxID_EVENT_TEST)
END_DECLARE_EVENT_TYPES()

#define EVT_TEST(fn) DECLARE_EVENT_TABLE_ENTRY( \
	wxEVT_TEST, wxID_ANY, wxID_ANY, (wxObjectEventFunction) (wxEventFunction) \
	(MyEventFunction) & fn, (wxObject *) NULL ),

MyEvent.cpp

#include "MyEvent.h"

DEFINE_EVENT_TYPE( wxEVT_TEST )
IMPLEMENT_DYNAMIC_CLASS(MyEvent, wxNotifyEvent)

MyEvent::MyEvent(wxEventType commandType, int id): wxNotifyEvent(commandType, id)
{
}

MyEvent::MyEvent(const MyEvent& event) : wxNotifyEvent(event)
{

}


MyEvent::~MyEvent(void)
{

}

void MyEvent::SetProgress(int progress)
{
 m_progress = progress;
}

int MyEvent::GetProgress(void)
{
    return m_progress;
}

W oknie głównym rejestruję event z wątku w taki sposób:

BEGIN_EVENT_TABLE(threadsDialog,wxDialog)
	EVT_TEST(threadsDialog::OnThreadEvent) 
END_EVENT_TABLE()

Start wątku po kliknięciu przycisku - wątek od razu startuje, bo metody startujące wątek są w jego konstruktorze:

this->testThread = new ::MyThread(this);

No i sama funkcja OnThreadEvent:

int progress = event.GetProgress();
	
	wxString p="";
	p.sprintf("%d",progress);

	this->progressLabel->SetLabel(p);

Co się dzieje? Ano zawsze po 4 iteracjach w wątku jest błąd Acces Violation i program natychmiastowo się wywala. Co tutaj jest nie tak? Jeżeli nie uzywam eventów, to wątek chodzi i wszystko jest OK. Potrzeba mi jednak przekazywac eventy do GUI i jest to jak się zdaje zalecana metoda...

Kompilator to Visual C++ 2008. Próbując skompilować to samo pod GCC na windows (przy użyciu Code::Blocks), mam z kolei błąd:

obj\Release\MyThread.o:MyThread.cpp:(.text+0x2d7)||undefined reference to __imp__wxEVT_TEST'| obj\Release\threadsMain.o:threadsMain.cpp:(.text+0x210c)||undefined reference to __imp__wxEVT_TEST'|

0

Nie mam możliwości szczegółowego przeanalizowania twojego kodu, więc nie powiem ci konkretnie co jest nie tak. Natomiast dokładnie taki mechanizm, czyli sterowanie paska postępu poprzez iwenty opisałem na swojej stronie. Co prawda tam jest inna klasa do obsługi wątku, ale może ci się to przyda.

0

Hmmm....

Warning: DOMDocument::load(http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd) [domdocument.load]: failed to open stream: HTTP request failed! in /home/c/h/o/chodnik/www/php/klasy.inc on line 9

Warning: DOMDocument::load() [domdocument.load]: failed to load external entity "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" in /home/c/h/o/chodnik/www/tpl/index.tpl, line: 2 in /home/c/h/o/chodnik/www/php/klasy.inc on line 9

Fatal error: Call to a member function appendChild() on a non-object in /home/c/h/o/chodnik/www/php/klasy.inc on line 21

@chodnik: czy tam jest dzialajacy minimalny przyklad? Z drugiej strony i tak jestem ciekawy co jest nie tak w moim kodzie :/

0

czy tam jest dzialajacy minimalny przyklad?

Pod tym linkiem jest projekt, w którym jest kod, o którym wspomniałem. Na pewno nie da się tego skompilować pod Linuxem, bo są tam odwołania do WinAPI. No i nie jest minimalny
http://www.chodnik.ovh.org/tut/fmg/img/wxManager.zip

Co do strony, to spróbuj wyłączyć javascript, może to coś pomoże, aczkolwiek u mnie działa niezależnie czy jest włączony czy wyłączony.

0
virtual wxEvent *Clone() const
{ 
	return new MyEvent(*this); 
}

[...]

MyEvent::MyEvent(const MyEvent& event) : wxNotifyEvent(event)
{
	/* a co z m_progress??? */
}

W zasadzie to nie musiałeś definiować własnej klasy zdarzenia, wystarczyło użyć wxCommandEvent z jego SetInt/GetInt.

wxString p=""; //<---- to przypisanie jest zupełnie zbyteczne
event.SetEventObject( (wxObject *)this->This() );

wxThread nie jest pochodną klasy wxObject, więc to rzutowanie jest błędem. Daj tam m_pParent lub NULL.

Co do samego błędu, to kompilator na ogół pokazuje miejsce, w który ów błąd wystąpił. Wystarczy prześledzić call stack, żeby zobaczyć w którym miejscu zaczynają się problemy.

0

wxThread nie jest pochodną klasy wxObject, więc to rzutowanie jest błędem. Daj tam m_pParent lub NULL.

Ale przeciez jako obiekt muszę wrzucić mój wątek - co mi z tego przyjdzie, jak podam tam NULL? Rzutować muszę, bo SetEventObject przyjmuje wxObject* jako argument.

Wystarczy prześledzić call stack, żeby zobaczyć w którym miejscu zaczynają się problemy.

Niby jak? Mam wxWidgets skompilowane jako release, debuggera nie ma.

Z kolei, błąd kompilacji pod GCC wskazuje chyba na jakieś przeoczenie? Tylko nie bardzo wiem gdzie :/

0

Ale przeciez jako obiekt muszę wrzucić mój wątek - co mi z tego przyjdzie, jak podam tam NULL? Rzutować muszę, bo SetEventObject przyjmuje wxObject* jako argument.

No bez żartów. Po pierwsze nic nie musisz, a po drugie, jakby była taka dowolność w ustawianiu tego parametru, to nie byłoby tam wskaźnika na wxObject tylko na void. A jeśli koniecznie chcesz tam coś ustawić, to ustaw m_pParent.

Niby jak? Mam wxWidgets skompilowane jako release, debuggera nie ma.

No to masz problem ;-)

0

Hmm ciekawa rzecz. Udało mi się skompilować w Code::Blocks, ale tam objawy są inne. Program się nie wywala, ale wygląda na to, że albo wątek nie startuje, albo eventy nie działają.
Co prawda podczas tworzenia wątku nie zwraca żadnych błędów, ale żadne eventy do okna głównego nie sa przekazywane.
Zastosowanie się do powyższych sugestii nic nie dało.

Z ciekawości spradzę jeszcze jak będzie działać to na linuksie, ale coś mi to wygląda na jakiś bug a samym wxWidgets

[edit]
Na linuksie jest dokladnie tak samo, jak na windowsie pod GCC - czyli zadnej reakcji... nawet w konsoli nic nie wypluwa. Tak jakby program sie nie walił. No coz, widze ze nikt nie bedzie wiedzial co jest nie tak (na forum wxWidgets tez nie wiedza).

0

Poświęciłem się i skompilowałem wxWidgets w trybie Debug. Wygląda na to, że program wywala się na metodzie wxStringData::IsEmpty() - z CallStack:

> test.exe!wxStringData::IsEmpty() Line 226 + 0x13 bytes C++

Z Disassembly:


 // empty string has a special ref count so it's never deleted
  bool  IsEmpty()   const { return (nRefs == -1); }
00401140  push        ebp  
00401141  mov         ebp,esp 
00401143  push        ecx  
00401144  mov         dword ptr [ebp-4],0CCCCCCCCh 
0040114B  mov         dword ptr [ebp-4],ecx 
0040114E  mov         eax,dword ptr [this] 
00401151  xor         ecx,ecx 
00401153  cmp         dword ptr [eax],0FFFFFFFFh <----- Tutaj
00401156  sete        cl   
00401159  mov         al,cl 
0040115B  mov         esp,ebp 
0040115D  pop         ebp  
0040115E  ret              

Co o tym myślicie? :>

0

Myślimy, że odwołujesz się do wxString'a, który nie istnieje ;-P Nie sądzisz, że dałeś za mało informacji? Bo z tych informacji, które podałeś można powiedzieć tylko to, co już napisałem. Zamiast wklejać fragment kodu assemblerowego mogłeś podać zawartość call stack'a, byłoby to znacznie bardziej pomocne.

0

Hmm jezeli sie odwoluje do nieistniejacego stringa, to raczej nie celowo, tylko dzieje sie to poza kodem, ktory napisalem :|

CallStack podalem, ale ok oto całość:

>	test.exe!wxStringData::IsEmpty()  Line 226 + 0x13 bytes	C++
 	test.exe!wxStringData::Unlock()  Line 243 + 0x17 bytes	C++
 	test.exe!wxStringBase::~wxStringBase()  Line 397	C++
 	test.exe!wxString::~wxString()  + 0x16 bytes	C++
 	test.exe!wxCommandEvent::~wxCommandEvent()  + 0x3f bytes	C++
 	test.exe!wxNotifyEvent::~wxNotifyEvent()  + 0x16 bytes	C++
 	test.exe!MyEvent::~MyEvent()  Line 19 + 0x8 bytes	C++
 	test.exe!threadsDialog::OnThreadEvent(MyEvent event={...})  Line 73 + 0x8 bytes	C++
 	test.exe!wxAppConsole::HandleEvent(wxEvtHandler * handler=0x00c76a50, void (wxEvent &)* func=0x0012fc30, wxEvent & event={...})  Line 323	C++
 	test.exe!wxEvtHandler::ProcessEvent(wxEvent & event={...})  Line 1293 + 0x1c bytes	C++
 	test.exe!wxEvtHandler::ProcessPendingEvents()  Line 1193	C++
 	test.exe!wxAppConsole::ProcessPendingEvents()  Line 296	C++
 	test.exe!wxIdleWakeUpModule::MsgHookProc(int nCode=0, unsigned int wParam=1, long lParam=1243812)  Line 6810	C++
 	user32.dll!7e381923() 	
 	[Frames below may be incorrect and/or missing, no symbols loaded for user32.dll]	
 	user32.dll!7e37b317() 	
 	user32.dll!7e3778d0() 	
 	ntdll.dll!7c90e453() 	
 	user32.dll!7e3818d1() 	
 	user32.dll!7e3691be() 	
 	user32.dll!7e37776b() 	
 	test.exe!GetMessage(tagMSG * lpMsg=0x0012fb44, HWND__ * hWnd=0x00000000, unsigned int wMsgFilterMin=0, unsigned int wMsgFilterMax=0)  Line 309	C++
 	test.exe!wxEventLoop::Dispatch()  Line 225 + 0xf bytes	C++
 	test.exe!wxEventLoopManual::Run()  Line 115 + 0xd bytes	C++
 	test.exe!wxDialogModalData::RunLoop()  Line 125	C++
 	test.exe!wxDialog::ShowModal()  Line 341	C++
 	test.exe!threadsApp::OnInit()  Line 29	C++
 	test.exe!wxAppConsole::CallOnInit()  Line 76 + 0x1e bytes	C++
 	test.exe!wxEntryReal(int & argc=1, char * * argv=0x003fccb0)  Line 444 + 0x1b bytes	C++
 	test.exe!wxEntry(int & argc=1, char * * argv=0x003fccb0)  Line 209 + 0xd bytes	C++
 	test.exe!wxEntry(HINSTANCE__ * hInstance=0x00400000, HINSTANCE__ * __formal=0x00000000, HINSTANCE__ * __formal=0x00000000, int nCmdShow=1)  Line 386 + 0xd bytes	C++
 	test.exe!WinMain(HINSTANCE__ * hInstance=0x00400000, HINSTANCE__ * hPrevInstance=0x00000000, char * lpCmdLine=0x00151f56, int nCmdShow=1)  Line 17 + 0x18 bytes	C++
 	test.exe!__tmainCRTStartup()  Line 574 + 0x1d bytes	C
 	kernel32.dll!7c817067() 	
 	test.exe!wxPluralFormsParser::logicalOrExpression()  Line 678 + 0x13 bytes	C++
 	000000ff()	

-		this	0xfffffff3 {nRefs=??? nDataLength=??? nAllocLength=??? }	const wxStringData * const
		nRefs	CXX0030: Error: expression cannot be evaluated	
		nDataLength	CXX0030: Error: expression cannot be evaluated	
		nAllocLength	CXX0030: Error: expression cannot be evaluated	

Dla mnie to za wiele nie mowi :|

0

O, już lepiej. Teraz pytanie za 100 punktów, co się dzieje w OnThreadEvent? Bo z tego co widać, tam jest usuwany MyEvent:

     <i>MyEvent::~MyEvent() Line 19 + 0x8 bytes  <span style="color: red"><---- !!!</span>
     threadsDialog::OnThreadEvent(MyEvent event={...})  Line 73 + 0x8 bytes</i>
     wxAppConsole::HandleEvent
     wxEvtHandler::ProcessEvent
     wxEvtHandler::ProcessPendingEvents()</i>

Nie wiem, czy chodzi o ten sam obiekt, ale jeśli to jest ten z parametru, to nie powinien być tam niszczony, bo metoda wxEvtHandler::ProcessPendingEvents niszczy go zaraz po wywołaniu wxEvtHandler::ProcessEvent (w każdym razie w mojej wersji wx'ów). Z drugiej strony to nie tłumaczy błędu, bo nawet jakbyś tam usuwał event, to wtedy błąd powinien wystąpić w ProcessPendingEvents. Dziwne :|

0

co się dzieje w OnThreadEvent? Bo z tego co widać, tam jest usuwany MyEvent

Ja go nie usuwam.. jak widac dzieje się to poza moim kodem :|

Te logi pochodzą z wxWidgets 2.8.9.

0

Jak możesz udostępnij exeka w wersji debug, zobaczę u siebie jak to działa.

0

Ok, tu jest skompilowany program: http://www.gosc.quotaless.com/test.rar
Wersja Debug (Multhi-Threated DLL), wxWidgets 2.8.9, Visual C++ 2008.

0

CRC Error :-/

0

Te darmowe hostingi mnie dobijają.... :/

http://giaur.freevar.com/test

Zmien rozszerzenie na .rar - stad powinno sie chyba sciagnac. A jak nie nie to wysle mailem...

0

Sprawdziłem i dziwna rzecz tam jest. OnThreadEvent nie zawiera nic poza wywołaniem destruktora MyEvent... no chyba że mi się coś pomyliło z identyfikacją metod.

0

OnThreadEvent nie zawiera nic poza wywołaniem destruktora MyEvent...

To możliwe - być może dla testów wykomentowałem całość MyEvent żeby sprawdzic, czy cos stamtad nie powoduje bledu (nie kojarze teraz, ktora wersja byla wyslana na serwer).

W każdym razie wywołanie destruktora występuje zawsze w tej metodzie, niezaleznie od tego co tam umieszcze i na pewno ja nigdzie nie wywoluje destruktora MyEvent

Wiec raczej wiele wiecej nie da sie ustalic? Moze zamieszcze jeszcze caly projekt w Visual C++?

Co ciekawsze, jeżeli nie będę definiować własnego typu eventu tylko użyje standardowego (jakiego - to nieistotne), a progress przepchne do GUI za pomoca na przykład wxEvent::SetInt() (tak jak to zrobili w dołączonym przykładzie z wątkanmi), to wszystko śmiga.

Tylko ze w taki sposob jak próbuję tutaj wyglądałoby to znacznie ładniej...

0

To możliwe - być może dla testów wykomentowałem całość MyEvent

A widzisz, a ja zachodzę w łeb, co to za metoda...

Wiec raczej wiele wiecej nie da sie ustalic?

Może i by się dało, ale za dużo czasu zajęłaby analiza.

Moze zamieszcze jeszcze caly projekt w Visual C++?

W sumie możesz, tylko uprzedzam, że ja mam unikodową wersję 2.7.2.

0

No coz, ja takiej analizy nie potrafie przeprowadzic (przyznaje sie bez bicia), a Ty nie masz wystarczajacej motywacji do tego (zrozumiale), wiec chyba trzeba bedzie zamknąć temat :P

W takim razie to chyba trzeba uznac za bug wxWidgets i to dosc paskudny

0

W takim razie to chyba trzeba uznac ze to bug wxWidgets i to dosc paskudny

Hmm, wątpię. Klasa wxEvtHandler jest dość stara, jakichś radykalnych zmian w niej nie zauważyłem w stosunku do wersji 2.7.2. Jeśli byłby błąd to już dawno ktoś by go wychwycił. Zresztą sam pisałeś, że ze standardowymi eventami działa, więc błąd jest u Ciebie.

0

Ale gdzie? I czemu nawet developerzy tej biblioteki nie wiedza? :| U mnie moze byc blad, bo ekspertem nie jestem. Problem w tym, ze nikt nie wie gdzie jest ten bląd - kod nie jest specjalnie skomplikowany

0

Ale gdzie?

Nie wiem. U mnie twój kod (z drobnymi poprawkami) działa bez zarzutu.

I czemu nawet developerzy tej biblioteki nie wiedza?

Bo mają za mało informacji. Błąd może być w innym miejscu (nie wiadomo co się dzieje w threadsDialog).

0

Wiem juz co powodowalo wywalanie się programu. Eventy muszą koniecznie być przekazywane przez referencję, czyli:

void threadsDialog::OnThreadEvent(MyEvent& event)

Mam wrazenie, ze dokumentacja nie precyzowala tego wyraznie, albo to ja cos przeoczylem.

@_0x666_
Tez dodales tylko referencje?

0

Mam wrazenie, ze dokumentacja nie precyzowala tego wyraznie

Takich rzeczy nie trzeba precyzować ;)

Tez dodales tylko referencje?

Nie tylko, jeszcze musiałem zmienić to i owo w MyEvent.

PS. podejrzewałem, że być może MyEvent przekazywałeś przez wartość, ale kiedy powiedziałeś, że ze standardowymi eventami chodzi, to założyłem, że prototyp handlera jest OK. Jak widać, nie był...

0

Przeciez prototyp MyEvent byl tu zamieszczony

Tyle że MyEvent jest tylko nośnikiem informacji o zdarzeniu, parametrem handlera. Handlerem jest threadsDialog::OnThreadEvent.

PS. na forum wx'ów też wytknęli ci rzutowanie wxThread na wxObject :>

0

Heh czytales? W sumie dla poczatkujacego dokumentacja taka jaka jest moze byc niewystarczajaca.. w koncu dlaczego dla mnie to mialo byc oczywiste ze event trzeba przekazac przez referencje? Skoro nie wiem jak dziala biblioteka od wewnatrz

0

w koncu dlaczego dla mnie to mialo byc oczywiste ze event trzeba przekazac przez referencje?

Ano dlatego, że parametrami wszystkich handlerów są pochodne klasy wxEvent, a klasy prawie zawsze przekazuje się w parametrach przez referencję. I to nie jest jakaś tam specyfika wx'ów, tylko tak to się robi w C++. Żeby było ciekawiej zdefiniowałeś wskaźnik na metodę, która ma w parametrze referencję:

typedef void (wxEvtHandler::*MyEventFunction)(MyEvent&);

No a że wx'y mają dość archaiczny system obsługi zdarzeń to i kompilator nie wychwycił tego dość zasadniczego błędu.

0

Ok mam jeszcze jedno pytanie. Czy do takiege eventa mozna bez obaw wrzucic cos innego niz tylko int? Chodzi mi o to, zeby mozna bylo wygodnie przekazywac do gui cos wiecej niz tylko progress (jako int).
Czy moze to byc na przyklad klasa, struktura lub wskaznik do niej? Bylaby to najwieksza zaleta takiego rozwiazania w porownaniu ze standardowymi eventami. Probowalem przekazywac strukture przez referencje i dziala, ale nie wiem czy to bezpieczne rozwiazanie?

1 użytkowników online, w tym zalogowanych: 0, gości: 1