Wean me off wxMutexGuiEnter

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
Virchanza
Experienced Solver
Experienced Solver
Posts: 78
Joined: Sun Jul 19, 2009 6:12 am

Wean me off wxMutexGuiEnter

Post by Virchanza » Sun Jun 06, 2010 10:12 am

OK so I'm developing a multi-threaded application.

There's two threads:
============================================================
Thread 1 : Main Thread:
This is the main GUI thread. The main GUI thread displays a dialog box. On this dialog box, there is a wxListCtrl which displays information sniffed from the network. For simplicity, I have a global variable which keeps track of the memory address of the main dialog box object:

Code: Select all

Dialog_Main *g_p_dlgmain;
Thread 2 : Network Packet Sniffer Thread:
This is the network packet sniffer thread. This thread runs in a loop sniffing packets from the network. Each time a packet that meets certain criteria is encountered, the contents of the wxListCtrl on the main dialog box need to be altered.
============================================================

Currently I've got this working fine using wxMutexGuiEnter/wxMutexGuiLeave, but I want to use AddPendingEvent instead... but I don't know what to do.

First of all, let me say that I'm using wxFormbuilder to design my GUI, and I have wxFormbuilder set to "Connect()" the events instead of using an event table. (I don't know if this prohibits me from using an even table for my custom events???)

There's three kinds of notification that my Sniffer thread has to make to the main GUI thread. The Sniffer makes these notifications by calling member functions which belong to the main dialog box object. These member functions deal with editing the contents of the wxListCtrl. Here they are:

Code: Select all

void Dialog_Main::Add_MAC_to_list(uintMAC);
void Dialog_Main::Update_IP_List(MAC_Data const *);
void Dialog_Main::Add_Port_to_list(MAC_Data const *,uintIP,uintPort);
Let me explain those three member functions:
1) The type "uintMAC" is just a typedef for uint_fast64_t
2) The type "MAC_Data" is a struct containing members which give all sorts of information about a MAC address
3) The types "uintIP" and "uintPort" are just typedef's (uint_fast32_t/uint_fast16_t)

From my Sniffer Thread, I invoke these member functions as follows:

Code: Select all

if ( we sniffed an interesting MAC address )
{
    wxMutexGuiEnter();
    g_p_dlgmain->Add_MAC_to_list(mac);
    wxMutexGuiLeave();
}

if ( we sniffed an interesting IP address )
{
    wxMutexGuiEnter();
    g_p_dlgmain->Update_IP_List(p_mac_data);
    wxMutexGuiLeave();
}

if ( we sniffed an interesting Port number )
{
    wxMutexGuiEnter();
    g_p_dlgmain->Add_Port_to_list(p_mac_data,ip,port);
    wxMutexGuiLeave();
}
I hope I explained that well enough.

OK so now I want to move away from using wxMutexGuiEnter, and I want to do things properly by using AddPendingEvent.

From what I understand, I need to create three custom events (corresponding to my 3 different kinds of notification). I've searched the web about how to do this but I've found pages that show 5 different ways of doing it. To be honest it's a bit daunting at first... I don't know what to do.

Can someone please help me out by posting minimalistic code for getting this done? I figure I have to create 3 custom wxEvent objects, something like:

Code: Select all

class MyEvent_Add_MAC_to_list : public wxEvent {
public:
    uintMAC mac;
};

class MyEvent_Update_IP_List : public wxEvent {
public:
    MAC_Data const *p;
};

class MyEven_Port_to_list : public wxEvent {
public:
    MAC_Data const *p;
    uintIP ip;
    uintPort port;
};
Then from my Sniffer Thread, I have to create these events and then invoke AddPendingEvent, something like:

Code: Select all

if ( we sniffed an interesting MAC address )
{
    MyEvent_Add_MAC_to_list x;
    
    x.mac = mac;

    g_p_dialogmain->GetEventHandler()->AddPendingEvent(x);
}

if ( we sniffed an interesting IP address )
{
    MyEvent_Update_IP_List x;
    
    x.p = p_mac_data;

    g_p_dialogmain->GetEventHandler()->AddPendingEvent(x);
}

if ( we sniffed an interesting Port number )
{
    MyEven_Port_to_list x;

    x.p = p_mac_data;
    x.ip = ip;
    x.port = port;

    g_p_dialogmain->GetEventHandler()->AddPendingEvent(x);
}
That's all I've got.

I've no idea how to "connect" the events in my main thread.

Can someone please help me out and post minimalistic code, both for my main thread and for the sniffer thread.

Thanks a lot for your time.

DavidHart
Site Admin
Site Admin
Posts: 4050
Joined: Thu Jan 12, 2006 6:23 pm
Location: IoW, UK

Post by DavidHart » Sun Jun 06, 2010 12:43 pm

Hi,
From what I understand, I need to create three custom events (corresponding to my 3 different kinds of notification)
Not necessarily. You will have to create one, that can carry the MAC_Data struct. However, since you're doing that, you may as well add more class members too, for the ints (though you might have been able to use the base-class wxCommandEvent::SetExtraLong and wxCommandEvent::SetInt for those).
I have wxFormbuilder set to "Connect()" the events instead of using an event table. (I don't know if this prohibits me from using an even table for my custom events???)
No, that's fine. If you Connect() a particular event to a function, that will take precedence over an identical event table entry; but if an event table entry doesn't have a corresponding Connect(), it'll just work as normal.
I've searched the web about how to do this but I've found pages that show 5 different ways of doing it.
I know, the wxWiki article really needs a prune.
Can someone please help me out and post minimalistic code, both for my main thread and for the sniffer thread.
I did, in http://wiki.wxwidgets.org/Custom_Events ... -_Method_4. Though that's not about threads, that doesn't matter: as you use AddPendingEvent(), or the equivalent wxPostEvent(), the same code works with or without threads. You just need to change the MyFooEvent to hold the data, and the copy ctor to copy it.

Regards,

David

Virchanza
Experienced Solver
Experienced Solver
Posts: 78
Joined: Sun Jul 19, 2009 6:12 am

Post by Virchanza » Mon Jun 07, 2010 1:57 pm

OK I've been working away trying to get this going.

I've written 1 custom event class that is utilised by all 3 notifications that come from my Sniffer thread (the ID of the event is used to identify the particular notification).
ID == 0 (Add_MAC_to_list)
ID == 1 (Update_IP_List)
ID == 2 (Add_Port_to_list)

I've added two files to my project:

sniffer_custom_wx_event.hpp
sniffer_custom_wx_event.cpp


Here's the header file:

Code: Select all

#ifndef HPP_SNIFFER_CUSTOM_WX_EVENT
#define HPP_SNIFFER_CUSTOM_WX_EVENT

#include <wx/wx.h>

#include "net_types.hpp"  /* uintMAC */
#include "dbase_basic_decls.hpp"  /* MAC_Data */

DECLARE_EVENT_TYPE( g_event_type_sniffer, -1 )  /* This line actually declares a global object */

class EventClass_Sniffer : public wxCommandEvent
{
public:

    MAC_Data const *p_mac_data;
    uintMAC mac;
    uintIP ip;
    uintPort port;

	EventClass_Sniffer( wxEventType arg_command_type = g_event_type_sniffer, int arg_id = 0 )
	:  wxCommandEvent(arg_command_type, arg_id), p_mac_data(0), mac(0), ip(0), port(0)  { }


    /* No need for copy-constructor, the default supplied one will do */


    // Required for sending with wxPostEvent()
	wxEvent *Clone() const
	{
	    return new EventClass_Sniffer(*this);
    }
};

typedef void (wxEvtHandler::*EventMemberFunctionPointer_Sniffer)(EventClass_Sniffer &);

// This #define simplifies the one below, and makes the syntax less
// ugly if you want to use Connect() instead of an event table.
#define EventHandler_Sniffer(func)                                         \
	(wxObjectEventFunction)(wxEventFunction)(wxCommandEventFunction)\
	wxStaticCastEvent(EventMemberFunctionPointer_Sniffer, &func)

// Define the event table entry. Yes, it really *does* end in a comma.
#define EVT_SNIFFER(id, fn)                                            \
	DECLARE_EVENT_TABLE_ENTRY( g_event_type_sniffer, id, wxID_ANY,  \
	(wxObjectEventFunction)(wxEventFunction)                     \
	(wxCommandEventFunction) wxStaticCastEvent(                  \
	EventMemberFunctionPointer_Sniffer, &fn ), (wxObject*) NULL ),

// Optionally, you can do a similar #define for EVT_SNIFFER_RANGE.
#define EVT_SNIFFER_RANGE(id1,id2, fn)                                 \
	DECLARE_EVENT_TABLE_ENTRY( g_event_type_sniffer, id1, id2,      \
	EventHandler_Sniffer(fn), (wxObject*) NULL ),

#endif
Here's the source file:

Code: Select all

#include "sniffer_custom_wx_event.hpp"

DEFINE_EVENT_TYPE( g_event_type_sniffer )
This custom event I've written will be sent from my Sniffer thread to my main GUI thread (it will be sent to the EventHandler of the main dialog window).

In the code for my main dialog window, I connect the event as follows:

Code: Select all

Dialog_Main::Dialog_Main( wxWindow* parent ) : Dialog_Main__Auto_Base_Class( parent )
{
    this->Connect( wxID_ANY,
                   g_event_type_sniffer,
                   EventHandler_Sniffer(Dialog_Main::OnSnifferCommunicate),
                   NULL,
                   this );
The event is received by the "OnSnifferCommunicate" member function belonging to my main dialog window, as follows:

Code: Select all

void Dialog_Main::OnSnifferCommunicate(EventClass_Sniffer &evt)
{
    switch ( evt.GetId() )
    {
    case 0: this->Add_MAC_to_list(evt.mac); break;

    case 1: this->Update_IP_List(evt.p_mac_data); break;

    case 2: this->Add_Port_to_list(evt.p_mac_data,evt.ip,evt.port); break;

    default:

        assert("The event ID can only be 0, 1 or 2" != 0);

    }
}
And finally, the event is actually sent from my Sniffer thread as follows:

(Remember that g_p_dlgmain is a global variable that holds the address of my main dialog window)

Code: Select all

if ( we sniffed an interesting MAC address )
{
    EventClass_Sniffer event(g_event_type_sniffer,0);

    event.mac = mac;

    g_p_dlgmain->GetEventHandler()->AddPendingEvent(event);
}

if ( we sniffed an interesting IP address )
{
    EventClass_Sniffer event(g_event_type_sniffer,1);

    event.p_mac_data = p_mac_data;

    g_p_dlgmain->GetEventHandler()->AddPendingEvent(event);
}

if ( we sniffed an interesting Port number )
{
    EventClass_Sniffer event(g_event_type_sniffer,2);

    event.p_mac_data = p_mac_data;
    event.ip = ip;
    event.port = port;

    g_p_dlgmain->GetEventHandler()->AddPendingEvent(event);
}
OK...

so...

how does all of that look?

Did I do anything wrong? Is there anything I could have done better?

I welcome all criticisms! :D

DavidHart
Site Admin
Site Admin
Posts: 4050
Joined: Thu Jan 12, 2006 6:23 pm
Location: IoW, UK

Post by DavidHart » Mon Jun 07, 2010 4:10 pm

/* No need for copy-constructor, the default supplied one will do */
That's incorrect (unless I've failed to notice some workaround).
Inside AddPendingEvent(), the event is cloned, using the copy ctor, and the cloned event is passed to the destination event handler. If you don't supply a copy ctor that copies your data, that cloned event arrives with the data at best null, and at worst undefined.

That's the only thing I immediately noticed. What happens when you try out the code, though?

Virchanza
Experienced Solver
Experienced Solver
Posts: 78
Joined: Sun Jul 19, 2009 6:12 am

Post by Virchanza » Tue Jun 08, 2010 9:43 am

This is taken from the 2003 C++ Standard (http://openassist.googlecode.com/files/ ... 202003.pdf)

Code: Select all

[Note: in some circumstances, C++ implementations implicitly define the default constructor (12.1), copy
constructor (12.8), assignment operator (12.8), or destructor (12.4) member functions. [Example: given

struct C {
    string s;
};
// string is the standard library class (clause 21)

int main()
{
    C a;
    C b = a;
    b = a;
}

the implementation will implicitly define functions to make the definition of C equivalent to

struct C {
    string s;
    C(): s() { }
    C(const C& x): s(x.s) { }
    C& operator=(const C& x) { s = x.s; return *this; }
    ~C() { }
};
If you look at the line that defines the copy-constructor, you'll see that all of the class's members get copied automatically.

These implicitly defined functions are only defined if you don't write your own declarations for them.

My program runs absolutely fine, thanks for asking. I just want to be sure though that I'm "following the rules" so that my code doesn't stop working on another platform in future.

If you can suggest a better way of doing anything, I'm all ears :)

Post Reply