]> git.saurik.com Git - wxWidgets.git/blame_incremental - src/common/sckipc.cpp
declare all NameStr[] strings as const char using the correct WXDLLIMPEXP_DATA_ macro...
[wxWidgets.git] / src / common / sckipc.cpp
... / ...
CommitLineData
1/////////////////////////////////////////////////////////////////////////////
2// Name: src/common/sckipc.cpp
3// Purpose: Interprocess communication implementation (wxSocket version)
4// Author: Julian Smart
5// Modified by: Guilhem Lavaux (big rewrite) May 1997, 1998
6// Guillermo Rodriguez (updated for wxSocket v2) Jan 2000
7// (callbacks deprecated) Mar 2000
8// Vadim Zeitlin (added support for Unix sockets) Apr 2002
9// (use buffering, many fixes/cleanup) Oct 2008
10// Created: 1993
11// RCS-ID: $Id$
12// Copyright: (c) Julian Smart 1993
13// (c) Guilhem Lavaux 1997, 1998
14// (c) 2000 Guillermo Rodriguez <guille@iies.es>
15// Licence: wxWindows licence
16/////////////////////////////////////////////////////////////////////////////
17
18// ==========================================================================
19// declarations
20// ==========================================================================
21
22// --------------------------------------------------------------------------
23// headers
24// --------------------------------------------------------------------------
25
26// For compilers that support precompilation, includes "wx.h".
27#include "wx/wxprec.h"
28
29#ifdef __BORLANDC__
30 #pragma hdrstop
31#endif
32
33#if wxUSE_SOCKETS && wxUSE_IPC && wxUSE_STREAMS
34
35#include "wx/sckipc.h"
36
37#ifndef WX_PRECOMP
38 #include "wx/log.h"
39 #include "wx/event.h"
40 #include "wx/module.h"
41#endif
42
43#include <stdlib.h>
44#include <stdio.h>
45#include <errno.h>
46
47#include "wx/socket.h"
48
49// --------------------------------------------------------------------------
50// macros and constants
51// --------------------------------------------------------------------------
52
53namespace
54{
55
56// Message codes
57enum IPCCode
58{
59 IPC_EXECUTE = 1,
60 IPC_REQUEST,
61 IPC_POKE,
62 IPC_ADVISE_START,
63 IPC_ADVISE_REQUEST,
64 IPC_ADVISE,
65 IPC_ADVISE_STOP,
66 IPC_REQUEST_REPLY,
67 IPC_FAIL,
68 IPC_CONNECT,
69 IPC_DISCONNECT
70};
71
72} // anonymous namespace
73
74// headers needed for umask()
75#ifdef __UNIX_LIKE__
76 #include <sys/types.h>
77 #include <sys/stat.h>
78#endif // __UNIX_LIKE__
79
80// ----------------------------------------------------------------------------
81// private functions
82// ----------------------------------------------------------------------------
83
84// get the address object for the given server name, the caller must delete it
85static wxSockAddress *
86GetAddressFromName(const wxString& serverName,
87 const wxString& host = wxEmptyString)
88{
89 // we always use INET sockets under non-Unix systems
90#if defined(__UNIX__) && !defined(__WINDOWS__) && !defined(__WINE__)
91 // under Unix, if the server name looks like a path, create a AF_UNIX
92 // socket instead of AF_INET one
93 if ( serverName.Find(_T('/')) != wxNOT_FOUND )
94 {
95 wxUNIXaddress *addr = new wxUNIXaddress;
96 addr->Filename(serverName);
97
98 return addr;
99 }
100#endif // Unix/!Unix
101 {
102 wxIPV4address *addr = new wxIPV4address;
103 addr->Service(serverName);
104 if ( !host.empty() )
105 {
106 addr->Hostname(host);
107 }
108
109 return addr;
110 }
111}
112
113// --------------------------------------------------------------------------
114// wxTCPEventHandler stuff (private class)
115// --------------------------------------------------------------------------
116
117class wxTCPEventHandler : public wxEvtHandler
118{
119public:
120 wxTCPEventHandler() : wxEvtHandler() {}
121
122 void Client_OnRequest(wxSocketEvent& event);
123 void Server_OnRequest(wxSocketEvent& event);
124
125 DECLARE_EVENT_TABLE()
126 DECLARE_NO_COPY_CLASS(wxTCPEventHandler)
127};
128
129enum
130{
131 _CLIENT_ONREQUEST_ID = 1000,
132 _SERVER_ONREQUEST_ID
133};
134
135static wxTCPEventHandler *gs_handler = NULL;
136
137// --------------------------------------------------------------------------
138// wxIPCSocketStreams
139// --------------------------------------------------------------------------
140
141#define USE_BUFFER
142
143// this class contains the various (related) streams used by wxTCPConnection
144// and also provides a way to read from the socket stream directly
145//
146// for writing to the stream use the IPCOutput class below
147class wxIPCSocketStreams
148{
149public:
150 // ctor initializes all the streams on top of the given socket
151 //
152 // note that we use a bigger than default buffer size which matches the
153 // typical Ethernet MTU
154 wxIPCSocketStreams(wxSocketBase& sock)
155 : m_socketStream(sock),
156#ifdef USE_BUFFER
157 m_bufferedOut(m_socketStream, 1500),
158#else
159 m_bufferedOut(m_socketStream),
160#endif
161 m_dataIn(m_socketStream),
162 m_dataOut(m_bufferedOut)
163 {
164 }
165
166 // expose the IO methods needed by IPC code (notice that writing is only
167 // done via IPCOutput)
168
169 // flush output
170 void Flush()
171 {
172#ifdef USE_BUFFER
173 m_bufferedOut.Sync();
174#endif
175 }
176
177 // simple wrappers around the functions with the same name in
178 // wxDataInputStream
179 wxUint8 Read8()
180 {
181 Flush();
182 return m_dataIn.Read8();
183 }
184
185 wxUint32 Read32()
186 {
187 Flush();
188 return m_dataIn.Read32();
189 }
190
191 wxString ReadString()
192 {
193 Flush();
194 return m_dataIn.ReadString();
195 }
196
197 // read arbitrary (size-prepended) data
198 //
199 // connection parameter is needed to call its GetBufferAtLeast() method
200 void *ReadData(wxConnectionBase *conn, size_t *size)
201 {
202 Flush();
203
204 wxCHECK_MSG( conn, NULL, "NULL connection parameter" );
205 wxCHECK_MSG( size, NULL, "NULL size parameter" );
206
207 *size = Read32();
208
209 void * const data = conn->GetBufferAtLeast(*size);
210 wxCHECK_MSG( data, NULL, "IPC buffer allocation failed" );
211
212 m_socketStream.Read(data, *size);
213
214 return data;
215 }
216
217 // same as above but for data preceded by the format
218 void *
219 ReadFormatData(wxConnectionBase *conn, wxIPCFormat *format, size_t *size)
220 {
221 wxCHECK_MSG( format, NULL, "NULL format parameter" );
222
223 *format = static_cast<wxIPCFormat>(Read8());
224
225 return ReadData(conn, size);
226 }
227
228
229 // these methods are only used by IPCOutput and not directly
230 wxDataOutputStream& GetDataOut() { return m_dataOut; }
231 wxOutputStream& GetUnformattedOut() { return m_bufferedOut; }
232
233private:
234 // this is the low-level underlying stream using the connection socket
235 wxSocketStream m_socketStream;
236
237 // the buffered stream is used to avoid writing all pieces of an IPC
238 // request to the socket one by one but to instead do it all at once when
239 // we're done with it
240#ifdef USE_BUFFER
241 wxBufferedOutputStream m_bufferedOut;
242#else
243 wxOutputStream& m_bufferedOut;
244#endif
245
246 // finally the data streams are used to be able to write typed data into
247 // the above streams easily
248 wxDataInputStream m_dataIn;
249 wxDataOutputStream m_dataOut;
250
251 DECLARE_NO_COPY_CLASS(wxIPCSocketStreams)
252};
253
254namespace
255{
256
257// an object of this class should be instantiated on the stack to write to the
258// underlying socket stream
259//
260// this class is intentionally separated from wxIPCSocketStreams to ensure that
261// Flush() is always called
262class IPCOutput
263{
264public:
265 // construct an object associated with the given streams (which must have
266 // life time greater than ours as we keep a reference to it)
267 IPCOutput(wxIPCSocketStreams *streams)
268 : m_streams(*streams)
269 {
270 wxASSERT_MSG( streams, "NULL streams pointer" );
271 }
272
273 // dtor calls Flush() really sending the IPC data to the network
274 ~IPCOutput() { m_streams.Flush(); }
275
276
277 // write a byte
278 void Write8(wxUint8 i)
279 {
280 m_streams.GetDataOut().Write8(i);
281 }
282
283 // write the reply code and a string
284 void Write(IPCCode code, const wxString& str)
285 {
286 Write8(code);
287 m_streams.GetDataOut().WriteString(str);
288 }
289
290 // write the reply code, a string and a format in this order
291 void Write(IPCCode code, const wxString& str, wxIPCFormat format)
292 {
293 Write(code, str);
294 Write8(format);
295 }
296
297 // write arbitrary data
298 void WriteData(const void *data, size_t size)
299 {
300 m_streams.GetDataOut().Write32(size);
301 m_streams.GetUnformattedOut().Write(data, size);
302 }
303
304
305private:
306 wxIPCSocketStreams& m_streams;
307
308 DECLARE_NO_COPY_CLASS(IPCOutput)
309};
310
311} // anonymous namespace
312
313// ==========================================================================
314// implementation
315// ==========================================================================
316
317IMPLEMENT_DYNAMIC_CLASS(wxTCPServer, wxServerBase)
318IMPLEMENT_DYNAMIC_CLASS(wxTCPClient, wxClientBase)
319IMPLEMENT_CLASS(wxTCPConnection, wxConnectionBase)
320
321// --------------------------------------------------------------------------
322// wxTCPClient
323// --------------------------------------------------------------------------
324
325wxTCPClient::wxTCPClient()
326 : wxClientBase()
327{
328}
329
330bool wxTCPClient::ValidHost(const wxString& host)
331{
332 wxIPV4address addr;
333
334 return addr.Hostname(host);
335}
336
337wxConnectionBase *wxTCPClient::MakeConnection(const wxString& host,
338 const wxString& serverName,
339 const wxString& topic)
340{
341 wxSockAddress *addr = GetAddressFromName(serverName, host);
342 if ( !addr )
343 return NULL;
344
345 wxSocketClient * const client = new wxSocketClient(wxSOCKET_WAITALL);
346 wxIPCSocketStreams * const streams = new wxIPCSocketStreams(*client);
347
348 bool ok = client->Connect(*addr);
349 delete addr;
350
351 if ( ok )
352 {
353 // Send topic name, and enquire whether this has succeeded
354 IPCOutput(streams).Write(IPC_CONNECT, topic);
355
356 unsigned char msg = streams->Read8();
357
358 // OK! Confirmation.
359 if (msg == IPC_CONNECT)
360 {
361 wxTCPConnection *
362 connection = (wxTCPConnection *)OnMakeConnection ();
363
364 if (connection)
365 {
366 if (connection->IsKindOf(CLASSINFO(wxTCPConnection)))
367 {
368 connection->m_topic = topic;
369 connection->m_sock = client;
370 connection->m_streams = streams;
371 client->SetEventHandler(*gs_handler, _CLIENT_ONREQUEST_ID);
372 client->SetClientData(connection);
373 client->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);
374 client->Notify(true);
375 return connection;
376 }
377 else
378 {
379 delete connection;
380 // and fall through to delete everything else
381 }
382 }
383 }
384 }
385
386 // Something went wrong, delete everything
387 delete streams;
388 client->Destroy();
389
390 return NULL;
391}
392
393wxConnectionBase *wxTCPClient::OnMakeConnection()
394{
395 return new wxTCPConnection();
396}
397
398// --------------------------------------------------------------------------
399// wxTCPServer
400// --------------------------------------------------------------------------
401
402wxTCPServer::wxTCPServer()
403 : wxServerBase()
404{
405 m_server = NULL;
406}
407
408bool wxTCPServer::Create(const wxString& serverName)
409{
410 // Destroy previous server, if any
411 if (m_server)
412 {
413 m_server->SetClientData(NULL);
414 m_server->Destroy();
415 m_server = NULL;
416 }
417
418 wxSockAddress *addr = GetAddressFromName(serverName);
419 if ( !addr )
420 return false;
421
422#ifdef __UNIX_LIKE__
423 mode_t umaskOld;
424 if ( addr->Type() == wxSockAddress::UNIX )
425 {
426 // ensure that the file doesn't exist as otherwise calling socket()
427 // would fail
428 int rc = remove(serverName.fn_str());
429 if ( rc < 0 && errno != ENOENT )
430 {
431 delete addr;
432
433 return false;
434 }
435
436 // also set the umask to prevent the others from reading our file
437 umaskOld = umask(077);
438 }
439 else
440 {
441 // unused anyhow but shut down the compiler warnings
442 umaskOld = 0;
443 }
444#endif // __UNIX_LIKE__
445
446 // Create a socket listening on the specified port (reusing it to allow
447 // restarting the server listening on the same port as was used by the
448 // previous instance of this server)
449 m_server = new wxSocketServer(*addr, wxSOCKET_WAITALL | wxSOCKET_REUSEADDR);
450
451#ifdef __UNIX_LIKE__
452 if ( addr->Type() == wxSockAddress::UNIX )
453 {
454 // restore the umask
455 umask(umaskOld);
456
457 // save the file name to remove it later
458 m_filename = serverName;
459 }
460#endif // __UNIX_LIKE__
461
462 delete addr;
463
464 if (!m_server->Ok())
465 {
466 m_server->Destroy();
467 m_server = NULL;
468
469 return false;
470 }
471
472 m_server->SetEventHandler(*gs_handler, _SERVER_ONREQUEST_ID);
473 m_server->SetClientData(this);
474 m_server->SetNotify(wxSOCKET_CONNECTION_FLAG);
475 m_server->Notify(true);
476
477 return true;
478}
479
480wxTCPServer::~wxTCPServer()
481{
482 if ( m_server )
483 {
484 m_server->SetClientData(NULL);
485 m_server->Destroy();
486 }
487
488#ifdef __UNIX_LIKE__
489 if ( !m_filename.empty() )
490 {
491 if ( remove(m_filename.fn_str()) != 0 )
492 {
493 wxLogDebug(_T("Stale AF_UNIX file '%s' left."), m_filename.c_str());
494 }
495 }
496#endif // __UNIX_LIKE__
497}
498
499wxConnectionBase *
500wxTCPServer::OnAcceptConnection(const wxString& WXUNUSED(topic))
501{
502 return new wxTCPConnection();
503}
504
505// --------------------------------------------------------------------------
506// wxTCPConnection
507// --------------------------------------------------------------------------
508
509void wxTCPConnection::Init()
510{
511 m_sock = NULL;
512 m_streams = NULL;
513}
514
515wxTCPConnection::~wxTCPConnection()
516{
517 Disconnect();
518
519 if ( m_sock )
520 {
521 m_sock->SetClientData(NULL);
522 m_sock->Destroy();
523 }
524
525 delete m_streams;
526}
527
528void wxTCPConnection::Compress(bool WXUNUSED(on))
529{
530 // TODO
531}
532
533// Calls that CLIENT can make.
534bool wxTCPConnection::Disconnect()
535{
536 if ( !GetConnected() )
537 return true;
538
539 // Send the disconnect message to the peer.
540 IPCOutput(m_streams).Write8(IPC_DISCONNECT);
541
542 if ( m_sock )
543 {
544 m_sock->Notify(false);
545 m_sock->Close();
546 }
547
548 SetConnected(false);
549
550 return true;
551}
552
553bool wxTCPConnection::DoExecute(const void *data,
554 size_t size,
555 wxIPCFormat format)
556{
557 if ( !m_sock->IsConnected() )
558 return false;
559
560 // Prepare EXECUTE message
561 IPCOutput out(m_streams);
562 out.Write8(IPC_EXECUTE);
563 out.Write8(format);
564
565 out.WriteData(data, size);
566
567 return true;
568}
569
570const void *wxTCPConnection::Request(const wxString& item,
571 size_t *size,
572 wxIPCFormat format)
573{
574 if ( !m_sock->IsConnected() )
575 return NULL;
576
577 IPCOutput(m_streams).Write(IPC_REQUEST, item, format);
578
579 int ret = m_streams->Read8();
580 if ( ret == IPC_FAIL )
581 return NULL;
582
583 return m_streams->ReadData(this, size);
584}
585
586bool wxTCPConnection::DoPoke(const wxString& item,
587 const void *data,
588 size_t size,
589 wxIPCFormat format)
590{
591 if ( !m_sock->IsConnected() )
592 return false;
593
594 IPCOutput out(m_streams);
595 out.Write(IPC_POKE, item, format);
596 out.WriteData(data, size);
597
598 return true;
599}
600
601bool wxTCPConnection::StartAdvise(const wxString& item)
602{
603 if ( !m_sock->IsConnected() )
604 return false;
605
606 IPCOutput(m_streams).Write(IPC_ADVISE_START, item);
607
608 int ret = m_streams->Read8();
609 if (ret != IPC_FAIL)
610 return true;
611 else
612 return false;
613}
614
615bool wxTCPConnection::StopAdvise (const wxString& item)
616{
617 if ( !m_sock->IsConnected() )
618 return false;
619
620 IPCOutput(m_streams).Write(IPC_ADVISE_STOP, item);
621
622 int ret = m_streams->Read8();
623
624 if (ret != IPC_FAIL)
625 return true;
626 else
627 return false;
628}
629
630// Calls that SERVER can make
631bool wxTCPConnection::DoAdvise(const wxString& item,
632 const void *data,
633 size_t size,
634 wxIPCFormat format)
635{
636 if ( !m_sock->IsConnected() )
637 return false;
638
639 IPCOutput out(m_streams);
640 out.Write(IPC_ADVISE, item, format);
641 out.WriteData(data, size);
642
643 return true;
644}
645
646// --------------------------------------------------------------------------
647// wxTCPEventHandler (private class)
648// --------------------------------------------------------------------------
649
650BEGIN_EVENT_TABLE(wxTCPEventHandler, wxEvtHandler)
651 EVT_SOCKET(_CLIENT_ONREQUEST_ID, wxTCPEventHandler::Client_OnRequest)
652 EVT_SOCKET(_SERVER_ONREQUEST_ID, wxTCPEventHandler::Server_OnRequest)
653END_EVENT_TABLE()
654
655void wxTCPEventHandler::Client_OnRequest(wxSocketEvent &event)
656{
657 wxSocketBase *sock = event.GetSocket();
658 if (!sock)
659 return ;
660
661 wxSocketNotify evt = event.GetSocketEvent();
662 wxTCPConnection *connection = (wxTCPConnection *)(sock->GetClientData());
663
664 // This socket is being deleted; skip this event
665 if (!connection)
666 return;
667
668 // We lost the connection: destroy everything
669 if (evt == wxSOCKET_LOST)
670 {
671 sock->Notify(false);
672 sock->Close();
673 connection->OnDisconnect();
674 return;
675 }
676
677 // Receive message number.
678 wxIPCSocketStreams * const streams = connection->m_streams;
679
680 const wxString topic = connection->m_topic;
681 wxString item;
682
683 bool error = false;
684
685 const int msg = streams->Read8();
686 switch ( msg )
687 {
688 case IPC_EXECUTE:
689 {
690 wxIPCFormat format;
691 size_t size wxDUMMY_INITIALIZE(0);
692 void * const
693 data = streams->ReadFormatData(connection, &format, &size);
694 if ( data )
695 connection->OnExecute(topic, data, size, format);
696 else
697 error = true;
698 }
699 break;
700
701 case IPC_ADVISE:
702 {
703 item = streams->ReadString();
704
705 wxIPCFormat format;
706 size_t size wxDUMMY_INITIALIZE(0);
707 void * const
708 data = streams->ReadFormatData(connection, &format, &size);
709
710 if ( data )
711 connection->OnAdvise(topic, item, data, size, format);
712 else
713 error = true;
714 }
715 break;
716
717 case IPC_ADVISE_START:
718 {
719 item = streams->ReadString();
720
721 IPCOutput(streams).Write8(connection->OnStartAdvise(topic, item)
722 ? IPC_ADVISE_START
723 : IPC_FAIL);
724 }
725 break;
726
727 case IPC_ADVISE_STOP:
728 {
729 item = streams->ReadString();
730
731 IPCOutput(streams).Write8(connection->OnStopAdvise(topic, item)
732 ? IPC_ADVISE_STOP
733 : IPC_FAIL);
734 }
735 break;
736
737 case IPC_POKE:
738 {
739 item = streams->ReadString();
740 wxIPCFormat format = (wxIPCFormat)streams->Read8();
741
742 size_t size wxDUMMY_INITIALIZE(0);
743 void * const data = streams->ReadData(connection, &size);
744
745 if ( data )
746 connection->OnPoke(topic, item, data, size, format);
747 else
748 error = true;
749 }
750 break;
751
752 case IPC_REQUEST:
753 {
754 item = streams->ReadString();
755
756 wxIPCFormat format = (wxIPCFormat)streams->Read8();
757
758 size_t user_size = wxNO_LEN;
759 const void *user_data = connection->OnRequest(topic,
760 item,
761 &user_size,
762 format);
763
764 if ( !user_data )
765 {
766 IPCOutput(streams).Write8(IPC_FAIL);
767 break;
768 }
769
770 IPCOutput out(streams);
771 out.Write8(IPC_REQUEST_REPLY);
772
773 if ( user_size == wxNO_LEN )
774 {
775 switch ( format )
776 {
777 case wxIPC_TEXT:
778 case wxIPC_UTF8TEXT:
779 user_size = strlen((const char *)user_data) + 1; // includes final NUL
780 break;
781 case wxIPC_UNICODETEXT:
782 user_size = (wcslen((const wchar_t *)user_data) + 1) * sizeof(wchar_t); // includes final NUL
783 break;
784 default:
785 user_size = 0;
786 }
787 }
788
789 out.WriteData(user_data, user_size);
790 }
791 break;
792
793 case IPC_DISCONNECT:
794 sock->Notify(false);
795 sock->Close();
796 connection->SetConnected(false);
797 connection->OnDisconnect();
798 break;
799
800 default:
801 wxLogDebug("Unknown message code %d received.", msg);
802 error = true;
803 break;
804 }
805
806 if ( error )
807 IPCOutput(streams).Write8(IPC_FAIL);
808}
809
810void wxTCPEventHandler::Server_OnRequest(wxSocketEvent &event)
811{
812 wxSocketServer *server = (wxSocketServer *) event.GetSocket();
813 if (!server)
814 return ;
815 wxTCPServer *ipcserv = (wxTCPServer *) server->GetClientData();
816
817 // This socket is being deleted; skip this event
818 if (!ipcserv)
819 return;
820
821 if (event.GetSocketEvent() != wxSOCKET_CONNECTION)
822 return;
823
824 // Accept the connection, getting a new socket
825 wxSocketBase *sock = server->Accept();
826 if (!sock)
827 return ;
828 if (!sock->Ok())
829 {
830 sock->Destroy();
831 return;
832 }
833
834 wxIPCSocketStreams *streams = new wxIPCSocketStreams(*sock);
835
836 {
837 IPCOutput out(streams);
838
839 const int msg = streams->Read8();
840 if ( msg == IPC_CONNECT )
841 {
842 const wxString topic = streams->ReadString();
843
844 wxTCPConnection *new_connection =
845 (wxTCPConnection *)ipcserv->OnAcceptConnection (topic);
846
847 if (new_connection)
848 {
849 if (new_connection->IsKindOf(CLASSINFO(wxTCPConnection)))
850 {
851 // Acknowledge success
852 out.Write8(IPC_CONNECT);
853
854 new_connection->m_sock = sock;
855 new_connection->m_streams = streams;
856 new_connection->m_topic = topic;
857 sock->SetEventHandler(*gs_handler, _CLIENT_ONREQUEST_ID);
858 sock->SetClientData(new_connection);
859 sock->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);
860 sock->Notify(true);
861 return;
862 }
863 else
864 {
865 delete new_connection;
866 // and fall through to delete everything else
867 }
868 }
869 }
870
871 // Something went wrong, send failure message and delete everything
872 out.Write8(IPC_FAIL);
873 } // IPCOutput object is destroyed here, before destroying stream
874
875 delete streams;
876 sock->Destroy();
877}
878
879// --------------------------------------------------------------------------
880// wxTCPEventHandlerModule (private class)
881// --------------------------------------------------------------------------
882
883class wxTCPEventHandlerModule: public wxModule
884{
885public:
886 virtual bool OnInit() { gs_handler = new wxTCPEventHandler; return true; }
887 virtual void OnExit() { wxDELETE(gs_handler); }
888
889 DECLARE_DYNAMIC_CLASS(wxTCPEventHandlerModule)
890};
891
892IMPLEMENT_DYNAMIC_CLASS(wxTCPEventHandlerModule, wxModule)
893
894#endif // wxUSE_SOCKETS && wxUSE_IPC && wxUSE_STREAMS