giovedì 19 agosto 2010

Intercettare gli eventi socket in un thread secondario

Questa seconda puntata è dedicata a un'altro argomento spinoso delle wxwidgets. In particolare vi fornirò un listato minimale sull'intercettazione di eventi socket in un thread secondario.
Purtroppo i thread non sono in grado di gestire eventi ed è del tutto inutile fare una doppia derivazione delle classi wxthread e wxEvtHandler. I listati sono piuttosto grezzi, incompleti, magari conterranno pure qualche errore, ma sono il primo prototipo funzionante che sono riuscito a scrivere.

Per raggiungere lo scopo occorre:
1) inserire la funzione non documentata wxSocketBase::Initialize(); prima di utilizzare le funzioni socket. E' consigliato inserirla all''inizio del software, in particolare io l'ho inserita all'interno della OnInit() dell'applicazione:

IMPLEMENT_APP(labApp);

bool labApp::OnInit()
{
    bool wxsOK = true;
    wxSocketBase::Initialize();

    if ( wxsOK )
    {
        labFrame* Frame = new labFrame(0);
        Frame->Show();
        SetTopWindow(Frame);
    }
    return wxsOK;
}

In secondo luogo dobbiamo creare una classe derivata da wxEvtHandler che permette di intercettare e gestire gli eventi. Questa classe l'ho chiamata SS_SocketEvents (NB: SS deriva dal mio nome e cognome). Contiene due funzioni, la OnSocketEvent e la OnServerEvent deputate rispettivamente ad intercettare gli eventi dei singoli socket che vengono aperti con la connessione dei vari client e gli eventi del server (fondalmentemente le nuove connessioni).

In terzo luogo dobbiamo creare il thread. Per brevità riporto il mio esempio così come l'ho scritto, in particolare ho chiamato la classe derivata IPCServer e riceve come argomento anche un puntatore di tipo wxEvtHandler per mantenere traccia dell'handler richiedente che ho utilizzato per inviare eventi dal thread alla gui principale, in particolare ho usato la funzione SendThreadMessage già vista in questo blog nella puntata precedente.

Infine scriviamo la EVENT_TABLE per intercettare gli eventi e lanciamo il thread:


file: sys.h

class SS_IPCServer;

class SS_SocketEvents : public wxEvtHandler
{
  public:
    SS_SocketEvents( SS_IPCServer * );
    void OnSocketEvent(wxSocketEvent& event);
    void OnServerEvent(wxSocketEvent& event);
    class SS_IPCServer * Parent;
  protected:
  DECLARE_EVENT_TABLE()
};

#define SS_IPC_Port 3000

enum
{
  IPC_SOCKET_ID = 100,
  IPC_SERVER_ID
};


class SS_IPCServer : public wxThread
{
public:
  SS_IPCServer(wxEvtHandler *);
  ~SS_IPCServer();

  void SetPort(int);
  void SendThreadMessage(wxString,int);
  virtual void * Entry();
  SS_SocketEvents * SoketEventHandler;

protected:

private:
    int s_TCP_port;
    wxSocketServer * s_server;
    int Start_TCP_Server();
    int s_numClients;
    wxEvtHandler * EH;
};


file: sys.cpp


BEGIN_EVENT_TABLE(SS_SocketEvents, wxEvtHandler)
EVT_SOCKET(IPC_SOCKET_ID, SS_SocketEvents::OnSocketEvent)
EVT_SOCKET(IPC_SERVER_ID, SS_SocketEvents::OnServerEvent)

END_EVENT_TABLE()



SS_SocketEvents::SS_SocketEvents(SS_IPCServer * Server) :wxEvtHandler()
{
  Parent=Server;
}

//============================================================

void SS_SocketEvents::OnSocketEvent(wxSocketEvent& event)
{
/*wxSOCKET_INPUT = 0
  wxSOCKET_OUTPUT = 1
  wxSOCKET_CONNECTION = 2
  wxSOCKET_LOST = 3*/

  this->Parent->SendThreadMessage(_("SOCKET EVENT !!!!"),IPCDebug);

  wxSocketBase * sock = (wxSocketBase*)event.GetClientData();

  int a=event.GetSocketEvent();
  this->Parent->SendThreadMessage((_("Received event code ")+IntTowxT(a)),IPCDebug);

if(sock)
{

  if ( event.GetSocketEvent() == wxSOCKET_INPUT ) {
    this->Parent->SendThreadMessage(_("SK wxSOCKET_INPUT !!!!!"),IPCDebug);
  }
  else if ( event.GetSocketEvent() == wxSOCKET_LOST ) {
    this->Parent->SendThreadMessage(_("SK wxSOCKET_LOST !!!!!"),IPCDebug);
  }
  else if ( event.GetSocketEvent() == wxSOCKET_CONNECTION ) {
    this->Parent->SendThreadMessage(_("SK wxSOCKET_CONNECTION !!!!!"),IPCDebug);
  }
  else this->Parent->SendThreadMessage(_("UNKNOWED SOCKET EVENT"),IPCDebug);
}

}


//============================================================

void SS_SocketEvents::OnServerEvent(wxSocketEvent& event)
{
/*wxSOCKET_INPUT = 0   wxSOCKET_OUTPUT = 1   wxSOCKET_CONNECTION = 2   wxSOCKET_LOST = 3*/

  this->Parent->SendThreadMessage(_("SERVER EVENT !!!!"),IPCDebug);

  wxSocketBase * sock = (wxSocketBase*)event.GetClientData();

  int a=event.GetSocketEvent();
  this->Parent->SendThreadMessage((_("Received event code ")+IntTowxT(a)),IPCDebug);

if(sock)
{

  if ( event.GetSocketEvent() == wxSOCKET_INPUT ) {
    this->Parent->SendThreadMessage(_("SK wxSOCKET_INPUT !!!!!"),IPCDebug);
  }
  else if ( event.GetSocketEvent() == wxSOCKET_LOST ) {
    this->Parent->SendThreadMessage(_("SK wxSOCKET_LOST !!!!!"),IPCDebug);
   }
  else if ( event.GetSocketEvent() == wxSOCKET_CONNECTION ) {
    this->Parent->SendThreadMessage(_("SK wxSOCKET_CONNECTION !!!!!"),IPCDebug);
  }
  else this->Parent->SendThreadMessage(_("UNKNOWED SOCKET EVENT"),IPCDebug);
}

}

//============================================================

SS_IPCServer::SS_IPCServer(wxEvtHandler *ev): wxThread(wxTHREAD_JOINABLE)
{
  s_TCP_port=0;
  s_server=NULL;
  EH=ev;
  SoketEventHandler=NULL;
}

//============================================================
SS_IPCServer::~SS_IPCServer()
{
  //Disconnect();
}

//============================================================
void* SS_IPCServer::Entry()
{
wxSocketBase * tempsok=NULL;
if(Start_TCP_Server()<0)
{
  SendThreadMessage(_("ERROR STARTING SOCKET THREAD"),IPCDebug);
  return(NULL);
}
while(1)
{
    if (TestDestroy())
    {
      delete s_server;
      break; // qui ci devi mettere la deallocazione
    }
    tempsok=s_server->Accept(false);
    if(tempsok) // new socketbase for client communication
    {
      SendThreadMessage(_("...incoming connection..."),IPCDebug);
      tempsok->SetClientData( (void*)this );
      tempsok->SetEventHandler(*SoketEventHandler, IPC_SOCKET_ID);
      tempsok->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG | wxSOCKET_CONNECTION); //
      tempsok->Notify(true);
    }
    tempsok=NULL;
    wxThread::Sleep(100);
}

}

//============================================================
void SS_IPCServer::SetPort(int port)
{
    s_TCP_port=port;
}

//============================================================

int SS_IPCServer::Start_TCP_Server()
{
  wxIPV4address Addr;
  Addr.Service(s_TCP_port);

  // Create the socket
  if(s_server) return(-1); // only 1 instance
  s_server = new wxSocketServer(Addr);
  if(!s_server) return(-2);
  if (! s_server->Ok()) return(-3); // We use Ok() here to see if the server is really listening
  s_server->SetClientData( (void*)this );
  SoketEventHandler=new SS_SocketEvents(this);
  if(!SoketEventHandler) SendThreadMessage(_("error creating SocketEventHandler"),IPCDebug);
  else{
    s_server->SetEventHandler(*SoketEventHandler, IPC_SERVER_ID);
    s_server->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG | wxSOCKET_CONNECTION);
    s_server->Notify(true);
  }
  s_numClients = 0;
  return(1);
}


file main.cpp // da qui viene lanciato il thread vero e proprio ed inizializzato il server.


    MyIPCServer=new SS_IPCServer(GetEventHandler());
    MyIPCServer->SetPort(SS_IPC_Port);
    if(MyIPCServer->Create((unsigned int)0)!=wxTHREAD_NO_ERROR) // lancia il thread che parte in modalità “suspended”. Lo 0 indica di usare lo stack di default
    {
      //ERROR creating IPC server
      delete MyIPCServer;
      return;
    }
    MyIPCServer->SetPriority(75); // setta la priorità del thread  (da 0 a 100 dove lo 0 indica la priorità minore.
    MyIPCServer->Run(); // questo lancia il thread*/


Spero di esservi stato utile anche se probabilmente ci sarà ancora qualche errore e qualche finezza da mettere a posto.

mercoledì 18 agosto 2010

Eventi personalizzati in un wxthread (custom wxevents on wxthread)

Comincio con l'inaugurare questo blog con un primo argomento che a suo tempo mi fece perdere parecchio tempo. Parto subito con il dire che programmo in C da tantissimi anni mentre digerisco male la programmazione C++. Da un annetto ho cominciato ad usare le librerie wxwidgets e l'IDE CodeBlock per svincolarmi dall'ormai vecchio Borland C++ Builder e poter cominciare a fare una programmazione multiplatform dei miei progetti. Devo ammettere che l'impatto è stato molto duro e cose che con il Borland facevo in modo semplice ed intuitivo, ora sono diventati veri e propri incubi.
I primi problemi nascono quando comincio a "far sul serio" e decido di convertire il software che stavo sviluppando per controllare l'impianto domotico di casa. Mi ritrovo a gestire i thread (wxThread) e, cosa che mi ha fatto impazzire, la creazione di wxEvent custom per permettermi di scambiare dati fra il thread principale del programma ed i thread secondari. La cosa bizzarra era peraltro che se facevo un progetto su un'unico file, anche se dopo numerosi tentativi, il tutto funzionava ma non riuscivo in alcun modo a portare il codice che gestiva gli eventi customizzati in un file separato....
Eccovi perciò un listato diviso in 4 files con tutto il necessario. Da una parte i frammenti dell'applicazione principale (labmain) e dall'altra il file che contengono la definizione sia del thread che dell'evento custom (sys). Abbiate pazienza ma non ho inserito gli include per il semplice motivo che non ricordo quali siano quelli fondamentali.

--------------------------------------------------------------------------------------------------
labmain.cpp
--------------------------------------------------------------------------------------------------

CustomThread * MyIPCServer=NULL;
labFrame::labFrame(wxWindow* parent,wxWindowID id)
{
.....
  Connect( wxID_ANY, E_IPCEvent,MyIPCEventHandler(labFrame::DoIPCEvent), NULL, this );
....
}


....lancio del thread da inserire in una funzione qualsiasi....

    MyIPCServer=new CustomThread(GetEventHandler());
    if(!MyIPCServer) {...;return;}
    if(MyIPCServer->Create((unsigned int)0)!=wxTHREAD_NO_ERROR) // lancia il thread che parte in modalità “suspended”. Lo 0 indica di usare lo stack di default
    {
      //ERROR
      delete MyIPCServer;
      return;
    }
    MyIPCServer->SetPriority(75); // setta la priorità del thread  (da 0 a 100 dove lo 0 indica la priorità minore.
    MyIPCServer->Run(); // questo lancia il thread*/
    //thread is running
.....


void labFrame::DoIPCEvent(C_IPCEvent &event )
{
    int ID=event.GetId();
    if (ID>=IPCUserMessage && ID<=IPCFailure)
    wxString Msg=event.GetText();

    if (ID==SendUserMessage) ....;
    else if (ID==SendError) ....;
    else if (ID==SendDebug) ....;
    else if (ID==SendVerbose) ....;
    else if (ID==SendFailure) ....;
}
    else ....;

    //SendUserMessage = 1,SendWarning, SendError, SendDebug, SendVerbose
}

--------------------------------------------------------------------------------------------------
labmain.h
--------------------------------------------------------------------------------------------------
class labFrame: public wxFrame
{
....
void DoIPCEvent(C_IPCEvent &event );
....
}


--------------------------------------------------------------------------------------------------
sys.cpp
--------------------------------------------------------------------------------------------------
DEFINE_EVENT_TYPE(E_IPCEvent)

....
void CustomThread::SendThreadMessage(wxString str,int type)
{
    if(EH)
    {
      C_IPCEvent event( E_IPCEvent, type );
      event.SetText(str);
      wxPostEvent( EH, event );
    }
}

....

CustomThread::CustomThread(wxEvtHandler *ev): wxThread(wxTHREAD_JOINABLE)
{
  EH=ev;
}
....

void* CustomThread::Entry()
{

  SendThreadMessage(_("this is a message from a thread"),IPCDebug);

while(1)
{
    if (TestDestroy())
    {
      ....
      break;
    }
    ....
    wxThread::Sleep(500);
}
}

--------------------------------------------------------------------------------------------------
sys.h
--------------------------------------------------------------------------------------------------

DECLARE_EVENT_TYPE( E_IPCEvent, -1 )

class C_IPCEvent: public wxCommandEvent
{
public:
    C_IPCEvent( wxEventType commandType = E_IPCEvent, int id = 0 ) : wxCommandEvent(commandType, id) { } 
    wxEvent* Clone() const { return new C_IPCEvent(*this); }

    wxString GetText() const { return m_Text; }
    void SetText( const wxString& text ) { m_Text = text;}

private:
    wxString m_Text;
};



typedef void (wxEvtHandler::*MyIPCEventFunction)(C_IPCEvent &);
#define MyIPCEventHandler(func) (wxObjectEventFunction)(wxEventFunction)(wxCommandEventFunction) wxStaticCastEvent(MyIPCEventFunction, &func)

enum
{
   IPCUserMessage = 1, IPCError, IPCDebug, IPCVerbose, IPCFailure
};

....

class CustomThread : public wxThread
{
public:
  CustomThread(wxEvtHandler *);
  ~CustomThread();
  void SendThreadMessage(wxString,int);
  virtual void * Entry();
protected:
private:
    wxEvtHandler * EH;
};

Spero che questo listato sia utile a tutti coloro che hanno disperatamente cercato una soluzione pronta al problema senza riuscire a trovarla.