]> git.saurik.com Git - wxWidgets.git/blob - src/common/sckipc.cpp
use buffered streams to reduce the number of TCP packets used per IPC command from...
[wxWidgets.git] / src / common / sckipc.cpp
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
53 namespace
54 {
55
56 // Message codes
57 enum 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
85 static wxSockAddress *
86 GetAddressFromName(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
117 class wxTCPEventHandler : public wxEvtHandler
118 {
119 public:
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
129 enum
130 {
131 _CLIENT_ONREQUEST_ID = 1000,
132 _SERVER_ONREQUEST_ID
133 };
134
135 static 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
147 class wxIPCSocketStreams
148 {
149 public:
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
233 private:
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
254 namespace
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
262 class IPCOutput
263 {
264 public:
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
305 private:
306 wxIPCSocketStreams& m_streams;
307
308 DECLARE_NO_COPY_CLASS(IPCOutput)
309 };
310
311 } // anonymous namespace
312
313 // ==========================================================================
314 // implementation
315 // ==========================================================================
316
317 IMPLEMENT_DYNAMIC_CLASS(wxTCPServer, wxServerBase)
318 IMPLEMENT_DYNAMIC_CLASS(wxTCPClient, wxClientBase)
319 IMPLEMENT_CLASS(wxTCPConnection, wxConnectionBase)
320
321 // --------------------------------------------------------------------------
322 // wxTCPClient
323 // --------------------------------------------------------------------------
324
325 wxTCPClient::wxTCPClient()
326 : wxClientBase()
327 {
328 }
329
330 bool wxTCPClient::ValidHost(const wxString& host)
331 {
332 wxIPV4address addr;
333
334 return addr.Hostname(host);
335 }
336
337 wxConnectionBase *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
393 wxConnectionBase *wxTCPClient::OnMakeConnection()
394 {
395 return new wxTCPConnection();
396 }
397
398 // --------------------------------------------------------------------------
399 // wxTCPServer
400 // --------------------------------------------------------------------------
401
402 wxTCPServer::wxTCPServer()
403 : wxServerBase()
404 {
405 m_server = NULL;
406 }
407
408 bool 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
480 wxTCPServer::~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
499 wxConnectionBase *
500 wxTCPServer::OnAcceptConnection(const wxString& WXUNUSED(topic))
501 {
502 return new wxTCPConnection();
503 }
504
505 // --------------------------------------------------------------------------
506 // wxTCPConnection
507 // --------------------------------------------------------------------------
508
509 void wxTCPConnection::Init()
510 {
511 m_sock = NULL;
512 m_streams = NULL;
513 }
514
515 wxTCPConnection::~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
528 void wxTCPConnection::Compress(bool WXUNUSED(on))
529 {
530 // TODO
531 }
532
533 // Calls that CLIENT can make.
534 bool 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
553 bool 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
570 const 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
586 bool 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
601 bool 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
615 bool 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
631 bool 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
650 BEGIN_EVENT_TABLE(wxTCPEventHandler, wxEvtHandler)
651 EVT_SOCKET(_CLIENT_ONREQUEST_ID, wxTCPEventHandler::Client_OnRequest)
652 EVT_SOCKET(_SERVER_ONREQUEST_ID, wxTCPEventHandler::Server_OnRequest)
653 END_EVENT_TABLE()
654
655 void 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 switch ( const int msg = streams->Read8() )
684 {
685 case IPC_EXECUTE:
686 {
687 wxIPCFormat format;
688 size_t size;
689 void * const
690 data = streams->ReadFormatData(connection, &format, &size);
691
692 connection->OnExecute(topic, data, size, format);
693 }
694 break;
695
696 case IPC_ADVISE:
697 {
698 item = streams->ReadString();
699
700 wxIPCFormat format;
701 size_t size;
702 void * const
703 data = streams->ReadFormatData(connection, &format, &size);
704
705 connection->OnAdvise(topic, item, data, size, format);
706 }
707 break;
708
709 case IPC_ADVISE_START:
710 {
711 item = streams->ReadString();
712
713 IPCOutput(streams).Write8(connection->OnStartAdvise(topic, item)
714 ? IPC_ADVISE_START
715 : IPC_FAIL);
716 }
717 break;
718
719 case IPC_ADVISE_STOP:
720 {
721 item = streams->ReadString();
722
723 IPCOutput(streams).Write8(connection->OnStopAdvise(topic, item)
724 ? IPC_ADVISE_STOP
725 : IPC_FAIL);
726 }
727 break;
728
729 case IPC_POKE:
730 {
731 item = streams->ReadString();
732 wxIPCFormat format = (wxIPCFormat)streams->Read8();
733
734 size_t size;
735 void * const data = streams->ReadData(connection, &size);
736
737 connection->OnPoke(topic, item, data, size, format);
738 }
739 break;
740
741 case IPC_REQUEST:
742 {
743 item = streams->ReadString();
744
745 wxIPCFormat format = (wxIPCFormat)streams->Read8();
746
747 size_t user_size = wxNO_LEN;
748 const void *user_data = connection->OnRequest(topic,
749 item,
750 &user_size,
751 format);
752
753 if ( !user_data )
754 {
755 IPCOutput(streams).Write8(IPC_FAIL);
756 break;
757 }
758
759 IPCOutput out(streams);
760 out.Write8(IPC_REQUEST_REPLY);
761
762 if ( user_size == wxNO_LEN )
763 {
764 switch ( format )
765 {
766 case wxIPC_TEXT:
767 case wxIPC_UTF8TEXT:
768 user_size = strlen((const char *)user_data) + 1; // includes final NUL
769 break;
770 case wxIPC_UNICODETEXT:
771 user_size = (wcslen((const wchar_t *)user_data) + 1) * sizeof(wchar_t); // includes final NUL
772 break;
773 default:
774 user_size = 0;
775 }
776 }
777
778 out.WriteData(user_data, user_size);
779 }
780 break;
781
782 case IPC_DISCONNECT:
783 sock->Notify(false);
784 sock->Close();
785 connection->SetConnected(false);
786 connection->OnDisconnect();
787 break;
788
789 default:
790 wxLogDebug("Unknown message code %d received.", msg);
791 IPCOutput(streams).Write8(IPC_FAIL);
792 break;
793 }
794 }
795
796 void wxTCPEventHandler::Server_OnRequest(wxSocketEvent &event)
797 {
798 wxSocketServer *server = (wxSocketServer *) event.GetSocket();
799 if (!server)
800 return ;
801 wxTCPServer *ipcserv = (wxTCPServer *) server->GetClientData();
802
803 // This socket is being deleted; skip this event
804 if (!ipcserv)
805 return;
806
807 if (event.GetSocketEvent() != wxSOCKET_CONNECTION)
808 return;
809
810 // Accept the connection, getting a new socket
811 wxSocketBase *sock = server->Accept();
812 if (!sock)
813 return ;
814 if (!sock->Ok())
815 {
816 sock->Destroy();
817 return;
818 }
819
820 wxIPCSocketStreams *streams = new wxIPCSocketStreams(*sock);
821
822 {
823 IPCOutput out(streams);
824
825 const int msg = streams->Read8();
826 if ( msg == IPC_CONNECT )
827 {
828 const wxString topic = streams->ReadString();
829
830 wxTCPConnection *new_connection =
831 (wxTCPConnection *)ipcserv->OnAcceptConnection (topic);
832
833 if (new_connection)
834 {
835 if (new_connection->IsKindOf(CLASSINFO(wxTCPConnection)))
836 {
837 // Acknowledge success
838 out.Write8(IPC_CONNECT);
839
840 new_connection->m_sock = sock;
841 new_connection->m_streams = streams;
842 new_connection->m_topic = topic;
843 sock->SetEventHandler(*gs_handler, _CLIENT_ONREQUEST_ID);
844 sock->SetClientData(new_connection);
845 sock->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);
846 sock->Notify(true);
847 return;
848 }
849 else
850 {
851 delete new_connection;
852 // and fall through to delete everything else
853 }
854 }
855 }
856
857 // Something went wrong, send failure message and delete everything
858 out.Write8(IPC_FAIL);
859 } // IPCOutput object is destroyed here, before destroying stream
860
861 delete streams;
862 sock->Destroy();
863 }
864
865 // --------------------------------------------------------------------------
866 // wxTCPEventHandlerModule (private class)
867 // --------------------------------------------------------------------------
868
869 class wxTCPEventHandlerModule: public wxModule
870 {
871 public:
872 virtual bool OnInit() { gs_handler = new wxTCPEventHandler; return true; }
873 virtual void OnExit() { wxDELETE(gs_handler); }
874
875 DECLARE_DYNAMIC_CLASS(wxTCPEventHandlerModule)
876 };
877
878 IMPLEMENT_DYNAMIC_CLASS(wxTCPEventHandlerModule, wxModule)
879
880 #endif // wxUSE_SOCKETS && wxUSE_IPC && wxUSE_STREAMS